Skip to content

Commit 71c5780

Browse files
committed
Documentation
1 parent c012f39 commit 71c5780

File tree

12 files changed

+739
-107
lines changed

12 files changed

+739
-107
lines changed

fuzzing-api/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,8 @@ plugins {
44

55
dependencies {
66
api(libs.managed.jazzer.api)
7+
compileOnly(mn.micronaut.core)
8+
9+
testImplementation(mnTest.junit.jupiter.engine)
10+
testImplementation(mnTest.junit.jupiter.params)
711
}
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/*
2+
* Copyright 2017-2025 original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micronaut.fuzzing.util;
17+
18+
import io.micronaut.core.annotation.NonNull;
19+
20+
import java.nio.charset.StandardCharsets;
21+
import java.util.Arrays;
22+
import java.util.Iterator;
23+
24+
/**
25+
* Utility class for splitting input bytes into pieces along a pre-defined separator.
26+
*/
27+
public final class ByteSplitter {
28+
private final byte[] separator;
29+
30+
private ByteSplitter(byte[] separator) {
31+
this.separator = separator;
32+
}
33+
34+
/**
35+
* Create a new splitter.
36+
*
37+
* @param separator The separator, will be UTF-8 decoded
38+
* @return The splitter
39+
*/
40+
@NonNull
41+
public static ByteSplitter create(@NonNull String separator) {
42+
return create(separator.getBytes(StandardCharsets.UTF_8));
43+
}
44+
45+
/**
46+
* Create a new splitter.
47+
*
48+
* @param separator The separator
49+
* @return The splitter
50+
*/
51+
@NonNull
52+
public static ByteSplitter create(byte @NonNull [] separator) {
53+
return new ByteSplitter(separator);
54+
}
55+
56+
/**
57+
* Split the given input. Convenience method that delegates to {@link #splitIterator(byte[])}.
58+
*
59+
* @param input The input to split
60+
* @return An iterable that iterates over the pieces
61+
*/
62+
@NonNull
63+
public Iterable<byte[]> split(@NonNull byte[] input) {
64+
return () -> splitIterator(input);
65+
}
66+
67+
/**
68+
* Split the given input.
69+
*
70+
* @param input The input to split
71+
* @return An iterator for the pieces
72+
*/
73+
@NonNull
74+
public ChunkIterator splitIterator(@NonNull byte[] input) {
75+
return new ChunkIterator(input);
76+
}
77+
78+
/**
79+
* Iterator for the split pieces.
80+
*/
81+
public class ChunkIterator implements Iterator<byte[]> {
82+
private final byte[] input;
83+
84+
private int index = -1;
85+
private int end = -separator.length;
86+
87+
private ChunkIterator(byte[] input) {
88+
this.input = input;
89+
}
90+
91+
private void findEnd() {
92+
for (int i = index; i < input.length - separator.length + 1; i++) {
93+
boolean match = true;
94+
for (int j = 0; j < separator.length; j++) {
95+
if (input[i + j] != separator[j]) {
96+
match = false;
97+
break;
98+
}
99+
}
100+
if (match) {
101+
end = i;
102+
return;
103+
}
104+
}
105+
end = input.length;
106+
}
107+
108+
/**
109+
* @return Whether there is another piece after the current one.
110+
*/
111+
@Override
112+
public boolean hasNext() {
113+
return end < input.length;
114+
}
115+
116+
/**
117+
* Proceed to the next piece. This behaves like {@link #next()}, except it doesn't return
118+
* the data array.
119+
*
120+
* @throws IllegalStateException if we reached end of input ({@link #hasNext()} returns
121+
* {@code false})
122+
*/
123+
public void proceed() {
124+
index = end + separator.length;
125+
if (index > input.length) {
126+
throw new IllegalStateException("No more bytes left");
127+
}
128+
findEnd();
129+
}
130+
131+
/**
132+
* Get the current piece as a byte array.
133+
*
134+
* @return Copy of the current piece
135+
* @throws IllegalStateException if {@link #proceed()} hasn't been called yet
136+
*/
137+
public byte[] asByteArray() {
138+
if (index < 0) {
139+
throw new IllegalStateException("Please call proceed() first");
140+
}
141+
return Arrays.copyOfRange(input, index, end);
142+
}
143+
144+
/**
145+
* Get the current piece as a string (UTF-8 decoded).
146+
*
147+
* @return Copy of the current piece
148+
* @throws IllegalStateException if {@link #proceed()} hasn't been called yet
149+
*/
150+
public String asString() {
151+
if (index < 0) {
152+
throw new IllegalStateException("Please call proceed() first");
153+
}
154+
return new String(input, index, end - index, StandardCharsets.UTF_8);
155+
}
156+
157+
/**
158+
* Get the start index of the current piece in the input array.
159+
*
160+
* @return The start index
161+
* @throws IllegalStateException if {@link #proceed()} hasn't been called yet
162+
*/
163+
public int start() {
164+
if (index < 0) {
165+
throw new IllegalStateException("Please call proceed() first");
166+
}
167+
return index;
168+
}
169+
170+
/**
171+
* Get the length of the current piece in the input array.
172+
*
173+
* @return The length
174+
* @throws IllegalStateException if {@link #proceed()} hasn't been called yet
175+
*/
176+
public int length() {
177+
if (index < 0) {
178+
throw new IllegalStateException("Please call proceed() first");
179+
}
180+
return end - index;
181+
}
182+
183+
/**
184+
* Proceed to the next piece and return it. Equivalent to
185+
* {@code proceed(); return asByteArray();}.
186+
*
187+
* @return The next piece
188+
*/
189+
@Override
190+
public byte @NonNull [] next() {
191+
proceed();
192+
return asByteArray();
193+
}
194+
}
195+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package io.micronaut.fuzzing.util;
2+
3+
import org.junit.jupiter.api.Assertions;
4+
import org.junit.jupiter.params.ParameterizedTest;
5+
import org.junit.jupiter.params.provider.ValueSource;
6+
7+
import java.nio.charset.StandardCharsets;
8+
9+
class ByteSplitterTest {
10+
@ParameterizedTest
11+
@ValueSource(strings = {
12+
"",
13+
"foo",
14+
"fooSEPbar",
15+
"fooSEPbarSEP",
16+
"fooSEPSEPbarSEP",
17+
})
18+
public void test(String input) {
19+
String[] expected = input.split("SEP", -1);
20+
ByteSplitter splitter = ByteSplitter.create("SEP");
21+
ByteSplitter.ChunkIterator iterator = splitter.splitIterator(input.getBytes(StandardCharsets.UTF_8));
22+
int i = 0;
23+
while (iterator.hasNext()) {
24+
iterator.proceed();
25+
String expectedPiece = expected[i++];
26+
Assertions.assertEquals(expectedPiece, iterator.asString());
27+
Assertions.assertArrayEquals(expectedPiece.getBytes(StandardCharsets.UTF_8), iterator.asByteArray());
28+
Assertions.assertEquals(expectedPiece, input.substring(iterator.start(), iterator.start() + iterator.length()));
29+
}
30+
}
31+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2017-2025 original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micronaut.fuzzing;
17+
18+
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
19+
import io.micronaut.fuzzing.runner.LocalJazzerRunner;
20+
21+
/**
22+
* Docs example.
23+
*/
24+
@FuzzTarget(enableImplicitly = false)
25+
public class IngredientTarget {
26+
public static void fuzzerTestOneInput(FuzzedDataProvider data) {
27+
Ingredient ingredient;
28+
try {
29+
ingredient = new Ingredient(data.consumeString(10), data.consumeInt(), data.consumeInt());
30+
} catch (IllegalArgumentException ok) {
31+
return;
32+
}
33+
if (ingredient.pricePerKg() <= 0) {
34+
throw new AssertionError();
35+
}
36+
}
37+
38+
record Ingredient(
39+
String name,
40+
int massInGrams,
41+
int priceInCents
42+
) {
43+
Ingredient {
44+
if (massInGrams == 0) {
45+
throw new IllegalArgumentException("Mass cannot be 0");
46+
}
47+
}
48+
49+
int pricePerKg() {
50+
return priceInCents * 1000 / massInGrams;
51+
}
52+
}
53+
54+
public static void main(String[] args) {
55+
LocalJazzerRunner.create(IngredientTarget.class).fuzz();
56+
}
57+
}

fuzzing-tests/src/main/java/io/micronaut/fuzzing/http/ByteSeparator.java

Lines changed: 0 additions & 59 deletions
This file was deleted.

0 commit comments

Comments
 (0)