diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f37b9c65d..b6b876985d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,26 @@ ## [Unreleased](https://github.com/aklivity/zilla/tree/HEAD) -[Full Changelog](https://github.com/aklivity/zilla/compare/0.9.56...HEAD) +[Full Changelog](https://github.com/aklivity/zilla/compare/0.9.57...HEAD) + +**Implemented enhancements:** + +- Integrate `http` binding with `validators` [\#455](https://github.com/aklivity/zilla/issues/455) + +**Fixed bugs:** + +- \[MQTT-Kafka\] Exception runtime.binding.mqtt.kafka.internal.types.MqttExpirySignalFW.wrap\(MqttExpirySignalFW.java:45\) [\#563](https://github.com/aklivity/zilla/issues/563) +- Running mqtt benchmark triggers mqtt exception [\#488](https://github.com/aklivity/zilla/issues/488) +- Fix IndexOutOfBoundsException when receiving expiry signal [\#567](https://github.com/aklivity/zilla/pull/567) ([bmaidics](https://github.com/bmaidics)) + +**Merged pull requests:** + +- Integrate http binding with validators [\#571](https://github.com/aklivity/zilla/pull/571) ([attilakreiner](https://github.com/attilakreiner)) +- Fix flow conrol bug + indexoutofbound exception [\#568](https://github.com/aklivity/zilla/pull/568) ([bmaidics](https://github.com/bmaidics)) + +## [0.9.57](https://github.com/aklivity/zilla/tree/0.9.57) (2023-11-04) + +[Full Changelog](https://github.com/aklivity/zilla/compare/0.9.56...0.9.57) **Fixed bugs:** diff --git a/README.md b/README.md index 9f4face008..324b6a4c6b 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ When Zilla is deployed alongside Apache Kafka®, any application or service can - [REST-Kafka Proxying](#rest-kafka-proxying) - [SSE-Kafka Proxying](#sse-kafka-proxying) - [gRPC-Kafka Proxying](#grpc-kafka-proxying) - - [MQTT-Kafka Proxying]((#mqtt-kafka-proxying)) + - [MQTT-Kafka Proxying](#mqtt-kafka-proxying) - [Resources](#resources) - [How Zilla Works](#how-zilla-works) - [FAQs](#faqs) diff --git a/build/flyweight-maven-plugin/pom.xml b/build/flyweight-maven-plugin/pom.xml index 4c9c4a0513..31e6a0bf8f 100644 --- a/build/flyweight-maven-plugin/pom.xml +++ b/build/flyweight-maven-plugin/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla build - 0.9.57 + 0.9.58 ../pom.xml diff --git a/build/pom.xml b/build/pom.xml index 7e423ff2c5..7ea2f4a017 100644 --- a/build/pom.xml +++ b/build/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla zilla - 0.9.57 + 0.9.58 ../pom.xml diff --git a/cloud/docker-image/pom.xml b/cloud/docker-image/pom.xml index dca796ab69..bcbccbbee8 100644 --- a/cloud/docker-image/pom.xml +++ b/cloud/docker-image/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla cloud - 0.9.57 + 0.9.58 ../pom.xml diff --git a/cloud/helm-chart/pom.xml b/cloud/helm-chart/pom.xml index 2bb31009bf..3598dbbdee 100644 --- a/cloud/helm-chart/pom.xml +++ b/cloud/helm-chart/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla cloud - 0.9.57 + 0.9.58 ../pom.xml diff --git a/cloud/pom.xml b/cloud/pom.xml index 8ca69d9a92..95b2b7be93 100644 --- a/cloud/pom.xml +++ b/cloud/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla zilla - 0.9.57 + 0.9.58 ../pom.xml diff --git a/conf/pom.xml b/conf/pom.xml index 82dd874a8b..2432643772 100644 --- a/conf/pom.xml +++ b/conf/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla zilla - 0.9.57 + 0.9.58 ../pom.xml diff --git a/incubator/binding-amqp.spec/pom.xml b/incubator/binding-amqp.spec/pom.xml index 6c658f2292..c8b0480a15 100644 --- a/incubator/binding-amqp.spec/pom.xml +++ b/incubator/binding-amqp.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.57 + 0.9.58 ../pom.xml diff --git a/incubator/binding-amqp/pom.xml b/incubator/binding-amqp/pom.xml index a7192a7558..5fa617061f 100644 --- a/incubator/binding-amqp/pom.xml +++ b/incubator/binding-amqp/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.57 + 0.9.58 ../pom.xml diff --git a/incubator/catalog-inline.spec/pom.xml b/incubator/catalog-inline.spec/pom.xml index cebdcc0fe3..b35c8f91b4 100644 --- a/incubator/catalog-inline.spec/pom.xml +++ b/incubator/catalog-inline.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.57 + 0.9.58 ../pom.xml diff --git a/incubator/catalog-inline/pom.xml b/incubator/catalog-inline/pom.xml index 5d804b362f..4057920616 100644 --- a/incubator/catalog-inline/pom.xml +++ b/incubator/catalog-inline/pom.xml @@ -6,7 +6,7 @@ io.aklivity.zilla incubator - 0.9.57 + 0.9.58 ../pom.xml diff --git a/incubator/catalog-schema-registry.spec/pom.xml b/incubator/catalog-schema-registry.spec/pom.xml index 55975f1f22..8dd0d7e197 100644 --- a/incubator/catalog-schema-registry.spec/pom.xml +++ b/incubator/catalog-schema-registry.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.57 + 0.9.58 ../pom.xml diff --git a/incubator/catalog-schema-registry/pom.xml b/incubator/catalog-schema-registry/pom.xml index 312505713e..bf4a7ba02a 100644 --- a/incubator/catalog-schema-registry/pom.xml +++ b/incubator/catalog-schema-registry/pom.xml @@ -6,7 +6,7 @@ io.aklivity.zilla incubator - 0.9.57 + 0.9.58 ../pom.xml diff --git a/incubator/command-dump/pom.xml b/incubator/command-dump/pom.xml index 490cdf34d7..1d6805e2bb 100644 --- a/incubator/command-dump/pom.xml +++ b/incubator/command-dump/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.57 + 0.9.58 ../pom.xml diff --git a/incubator/command-generate/pom.xml b/incubator/command-generate/pom.xml index 4804020de3..1b5e772743 100644 --- a/incubator/command-generate/pom.xml +++ b/incubator/command-generate/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.57 + 0.9.58 ../pom.xml diff --git a/incubator/command-log/pom.xml b/incubator/command-log/pom.xml index 41887129d4..98133854c9 100644 --- a/incubator/command-log/pom.xml +++ b/incubator/command-log/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.57 + 0.9.58 ../pom.xml diff --git a/incubator/command-tune/pom.xml b/incubator/command-tune/pom.xml index 7cff3b2326..29b1815461 100644 --- a/incubator/command-tune/pom.xml +++ b/incubator/command-tune/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.57 + 0.9.58 ../pom.xml diff --git a/incubator/exporter-otlp.spec/pom.xml b/incubator/exporter-otlp.spec/pom.xml index bf2150f476..e6fae67081 100644 --- a/incubator/exporter-otlp.spec/pom.xml +++ b/incubator/exporter-otlp.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.57 + 0.9.58 ../pom.xml diff --git a/incubator/exporter-otlp/pom.xml b/incubator/exporter-otlp/pom.xml index b68e5855c0..ab1062ee58 100644 --- a/incubator/exporter-otlp/pom.xml +++ b/incubator/exporter-otlp/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.57 + 0.9.58 ../pom.xml diff --git a/incubator/pom.xml b/incubator/pom.xml index e1fd785390..89295b4b09 100644 --- a/incubator/pom.xml +++ b/incubator/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla zilla - 0.9.57 + 0.9.58 ../pom.xml diff --git a/incubator/validator-avro.spec/pom.xml b/incubator/validator-avro.spec/pom.xml index df003773c5..6fb3910952 100644 --- a/incubator/validator-avro.spec/pom.xml +++ b/incubator/validator-avro.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.57 + 0.9.58 ../pom.xml diff --git a/incubator/validator-avro/pom.xml b/incubator/validator-avro/pom.xml index 6d20550c48..ac48daa880 100644 --- a/incubator/validator-avro/pom.xml +++ b/incubator/validator-avro/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.57 + 0.9.58 ../pom.xml diff --git a/incubator/validator-core.spec/pom.xml b/incubator/validator-core.spec/pom.xml index 1cc3a59c23..fb36f493af 100644 --- a/incubator/validator-core.spec/pom.xml +++ b/incubator/validator-core.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.57 + 0.9.58 ../pom.xml diff --git a/incubator/validator-core/pom.xml b/incubator/validator-core/pom.xml index 44bd289ddb..20f0ffb89a 100644 --- a/incubator/validator-core/pom.xml +++ b/incubator/validator-core/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.57 + 0.9.58 ../pom.xml diff --git a/incubator/validator-json.spec/pom.xml b/incubator/validator-json.spec/pom.xml index 6e9f0c6f2c..b39c3eab8c 100644 --- a/incubator/validator-json.spec/pom.xml +++ b/incubator/validator-json.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.57 + 0.9.58 ../pom.xml diff --git a/incubator/validator-json/pom.xml b/incubator/validator-json/pom.xml index f059036bf8..ca82a65d49 100644 --- a/incubator/validator-json/pom.xml +++ b/incubator/validator-json/pom.xml @@ -6,7 +6,7 @@ io.aklivity.zilla incubator - 0.9.57 + 0.9.58 ../pom.xml diff --git a/manager/pom.xml b/manager/pom.xml index ffd39ff674..e1bf2e8831 100644 --- a/manager/pom.xml +++ b/manager/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla zilla - 0.9.57 + 0.9.58 ../pom.xml diff --git a/pom.xml b/pom.xml index f71cf47830..a292f3bb81 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ 4.0.0 io.aklivity.zilla zilla - 0.9.57 + 0.9.58 pom zilla https://github.com/aklivity/zilla diff --git a/runtime/binding-echo/pom.xml b/runtime/binding-echo/pom.xml index b6dac2889d..e511abb7cd 100644 --- a/runtime/binding-echo/pom.xml +++ b/runtime/binding-echo/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/binding-fan/pom.xml b/runtime/binding-fan/pom.xml index 24c15623b6..dbe707a835 100644 --- a/runtime/binding-fan/pom.xml +++ b/runtime/binding-fan/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/binding-filesystem/pom.xml b/runtime/binding-filesystem/pom.xml index 65fe10297f..0ef11ab9da 100644 --- a/runtime/binding-filesystem/pom.xml +++ b/runtime/binding-filesystem/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/binding-grpc-kafka/pom.xml b/runtime/binding-grpc-kafka/pom.xml index bd590c0763..47e55e8fba 100644 --- a/runtime/binding-grpc-kafka/pom.xml +++ b/runtime/binding-grpc-kafka/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/binding-grpc/pom.xml b/runtime/binding-grpc/pom.xml index 4e208474f4..e8c841fc25 100644 --- a/runtime/binding-grpc/pom.xml +++ b/runtime/binding-grpc/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/binding-http-filesystem/pom.xml b/runtime/binding-http-filesystem/pom.xml index 25296f739b..510e883ea5 100644 --- a/runtime/binding-http-filesystem/pom.xml +++ b/runtime/binding-http-filesystem/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/binding-http-kafka/pom.xml b/runtime/binding-http-kafka/pom.xml index fa1cb0b715..c36b7970eb 100644 --- a/runtime/binding-http-kafka/pom.xml +++ b/runtime/binding-http-kafka/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/binding-http/pom.xml b/runtime/binding-http/pom.xml index bef699cce8..ac6833acbd 100644 --- a/runtime/binding-http/pom.xml +++ b/runtime/binding-http/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml @@ -137,7 +137,7 @@ - io/aklivity/zilla/specs/binding/http/schema/http*.schema.patch.json + io/aklivity/zilla/specs/binding/http/schema/http.schema.patch.json ${project.build.directory}/classes diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpRequestConfig.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpRequestConfig.java index 879f3dfb8b..f8b97422da 100644 --- a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpRequestConfig.java +++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpRequestConfig.java @@ -35,13 +35,13 @@ public enum Method TRACE } - public String path; - public Method method; - public List contentType; - public List headers; - public List pathParams; - public List queryParams; - public ValidatorConfig content; + public final String path; + public final Method method; + public final List contentType; + public final List headers; + public final List pathParams; + public final List queryParams; + public final ValidatorConfig content; public HttpRequestConfig( String path, diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/HttpBinding.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/HttpBinding.java index e020b8c536..f274b56bd5 100644 --- a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/HttpBinding.java +++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/HttpBinding.java @@ -41,10 +41,7 @@ public String name() @Override public URL type() { - String patch = config.requestValidators() - ? "schema/http.with.validators.schema.patch.json" - : "schema/http.schema.patch.json"; - return getClass().getResource(patch); + return getClass().getResource("schema/http.schema.patch.json"); } @Override diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/HttpConfiguration.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/HttpConfiguration.java index 81c5b7b571..58df0ec1cd 100644 --- a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/HttpConfiguration.java +++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/HttpConfiguration.java @@ -35,7 +35,6 @@ public class HttpConfiguration extends Configuration public static final IntPropertyDef HTTP_MAX_CONCURRENT_APPLICATION_HEADERS; public static final PropertyDef HTTP_SERVER_HEADER; public static final PropertyDef HTTP_USER_AGENT_HEADER; - public static final BooleanPropertyDef HTTP_REQUEST_VALIDATORS; private static final ConfigurationDef HTTP_CONFIG; @@ -53,7 +52,6 @@ public class HttpConfiguration extends Configuration HTTP_MAX_CONCURRENT_STREAMS_CLEANUP = config.property("max.concurrent.streams.cleanup", 1000); HTTP_STREAMS_CLEANUP_DELAY = config.property("streams.cleanup.delay", 100); HTTP_MAX_CONCURRENT_APPLICATION_HEADERS = config.property("max.concurrent.application.headers", 10000); - HTTP_REQUEST_VALIDATORS = config.property("request.validators", false); HTTP_CONFIG = config; } @@ -115,11 +113,6 @@ public int maxConcurrentApplicationHeaders() return HTTP_MAX_CONCURRENT_APPLICATION_HEADERS.getAsInt(this); } - public boolean requestValidators() - { - return HTTP_REQUEST_VALIDATORS.getAsBoolean(this); - } - public String16FW serverHeader() { return serverHeader; diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/config/HttpBindingConfig.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/config/HttpBindingConfig.java index 9bbe39aa86..74936c27c3 100644 --- a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/config/HttpBindingConfig.java +++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/config/HttpBindingConfig.java @@ -19,21 +19,38 @@ import static java.util.EnumSet.allOf; import static java.util.stream.Collectors.toList; +import java.util.HashMap; +import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.SortedSet; +import java.util.TreeMap; import java.util.TreeSet; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.ToLongFunction; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.agrona.DirectBuffer; +import org.agrona.collections.MutableBoolean; +import org.agrona.collections.Object2ObjectHashMap; + import io.aklivity.zilla.runtime.binding.http.config.HttpAccessControlConfig; import io.aklivity.zilla.runtime.binding.http.config.HttpCredentialsConfig; import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpParamConfig; import io.aklivity.zilla.runtime.binding.http.config.HttpPatternConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpRequestConfig; import io.aklivity.zilla.runtime.binding.http.config.HttpVersion; +import io.aklivity.zilla.runtime.binding.http.internal.types.HttpHeaderFW; +import io.aklivity.zilla.runtime.binding.http.internal.types.String16FW; +import io.aklivity.zilla.runtime.binding.http.internal.types.String8FW; +import io.aklivity.zilla.runtime.binding.http.internal.types.stream.HttpBeginExFW; import io.aklivity.zilla.runtime.engine.config.BindingConfig; import io.aklivity.zilla.runtime.engine.config.KindConfig; +import io.aklivity.zilla.runtime.engine.config.ValidatorConfig; +import io.aklivity.zilla.runtime.engine.validator.Validator; public final class HttpBindingConfig { @@ -41,6 +58,10 @@ public final class HttpBindingConfig private static final SortedSet DEFAULT_VERSIONS = new TreeSet<>(allOf(HttpVersion.class)); private static final HttpAccessControlConfig DEFAULT_ACCESS_CONTROL = HttpAccessControlConfig.builder().policy(SAME_ORIGIN).build(); + private static final String8FW HEADER_CONTENT_TYPE = new String8FW("content-type"); + private static final String8FW HEADER_METHOD = new String8FW(":method"); + private static final String8FW HEADER_PATH = new String8FW(":path"); + private static final HttpQueryStringComparator QUERY_STRING_COMPARATOR = new HttpQueryStringComparator(); public final long id; public final String name; @@ -49,9 +70,17 @@ public final class HttpBindingConfig public final List routes; public final ToLongFunction resolveId; public final Function, String> credentials; + public final List requests; public HttpBindingConfig( BindingConfig binding) + { + this(binding, null); + } + + public HttpBindingConfig( + BindingConfig binding, + BiFunction, Validator> createValidator) { this.id = binding.id; this.name = binding.name; @@ -61,6 +90,7 @@ public HttpBindingConfig( this.resolveId = binding.resolveId; this.credentials = options != null && options.authorization != null ? asAccessor(options.authorization.credentials) : DEFAULT_CREDENTIALS; + this.requests = createValidator == null ? null : createRequestTypes(createValidator); } public HttpRouteConfig resolve( @@ -164,6 +194,188 @@ private Function, String> asAccessor( return accessor; } + private List createRequestTypes( + BiFunction, Validator> createValidator) + { + List requestTypes = new LinkedList<>(); + if (this.options != null && this.options.requests != null) + { + for (HttpRequestConfig request : this.options.requests) + { + Map headers = new HashMap<>(); + if (request.headers != null) + { + for (HttpParamConfig header : request.headers) + { + headers.put(new String8FW(header.name), createValidator.apply(header.validator, this.resolveId)); + } + } + Map pathParams = new Object2ObjectHashMap<>(); + if (request.pathParams != null) + { + for (HttpParamConfig pathParam : request.pathParams) + { + pathParams.put(pathParam.name, createValidator.apply(pathParam.validator, this.resolveId)); + } + } + Map queryParams = new TreeMap<>(QUERY_STRING_COMPARATOR); + if (request.queryParams != null) + { + for (HttpParamConfig queryParam : request.queryParams) + { + queryParams.put(queryParam.name, createValidator.apply(queryParam.validator, this.resolveId)); + } + } + Validator content = createValidator.apply(request.content, this.resolveId); + HttpRequestType requestType = HttpRequestType.builder() + .path(request.path) + .method(request.method) + .contentType(request.contentType) + .headers(headers) + .pathParams(pathParams) + .queryParams(queryParams) + .content(content) + .build(); + requestTypes.add(requestType); + } + } + return requestTypes; + } + + public HttpRequestType resolveRequestType( + HttpBeginExFW beginEx) + { + HttpRequestType result = null; + if (requests != null && !requests.isEmpty()) + { + String path = resolveHeaderValue(beginEx, HEADER_PATH); + String method = resolveHeaderValue(beginEx, HEADER_METHOD); + String contentType = resolveHeaderValue(beginEx, HEADER_CONTENT_TYPE); + for (HttpRequestType requestType : requests) + { + if (matchMethod(requestType, method) && + matchContentType(requestType, contentType) && + matchPath(requestType, path)) + { + result = requestType; + break; + } + } + } + return result; + } + + private boolean matchMethod( + HttpRequestType requestType, + String method) + { + return method == null || requestType.method == null || method.equals(requestType.method.name()); + } + + private boolean matchContentType( + HttpRequestType requestType, + String contentType) + { + return contentType == null || requestType.contentType == null || requestType.contentType.contains(contentType); + } + + private boolean matchPath( + HttpRequestType requestType, + String path) + { + return requestType.pathMatcher.reset(path).matches(); + } + + public boolean validateHeaders( + HttpRequestType requestType, + HttpBeginExFW beginEx) + { + String path = beginEx.headers().matchFirst(h -> h.name().equals(HEADER_PATH)).value().asString(); + return requestType == null || + validateHeaderValues(requestType, beginEx) && + validatePathParams(requestType, path) && + validateQueryParams(requestType, path); + } + + private boolean validateHeaderValues( + HttpRequestType requestType, + HttpBeginExFW beginEx) + { + MutableBoolean valid = new MutableBoolean(true); + if (requestType != null && requestType.headers != null) + { + beginEx.headers().forEach(header -> + { + if (valid.value) + { + Validator validator = requestType.headers.get(header.name()); + if (validator != null) + { + String16FW value = header.value(); + valid.value &= validator.read(value.value(), value.offset(), value.length()); + } + } + }); + } + return valid.value; + } + + private boolean validatePathParams( + HttpRequestType requestType, + String path) + { + Matcher matcher = requestType.pathMatcher.reset(path); + boolean matches = matcher.matches(); + assert matches; + + boolean valid = true; + for (String name : requestType.pathParams.keySet()) + { + String value = matcher.group(name); + if (value != null) + { + String8FW value0 = new String8FW(value); + Validator validator = requestType.pathParams.get(name); + if (!validator.read(value0.value(), value0.offset(), value0.length())) + { + valid = false; + break; + } + } + } + return valid; + } + + private boolean validateQueryParams( + HttpRequestType requestType, + String path) + { + Matcher matcher = requestType.queryMatcher.reset(path); + boolean valid = true; + while (valid && matcher.find()) + { + String name = matcher.group(1); + Validator validator = requestType.queryParams.get(name); + if (validator != null) + { + String8FW value = new String8FW(matcher.group(2)); + valid &= validator.read(value.value(), value.offset(), value.length()); + } + } + return valid; + } + + public boolean validateContent( + HttpRequestType requestType, + DirectBuffer buffer, + int index, + int length) + { + return requestType == null || + requestType.content == null || + requestType.content.read(buffer, index, length); + } + private static Function, String> orElseIfNull( Function, String> first, Function, String> second) @@ -174,4 +386,17 @@ private static Function, String> orElseIfNull( return result != null ? result : second.apply(hs); }; } + + private static String resolveHeaderValue( + HttpBeginExFW beginEx, + String8FW headerName) + { + String result = null; + HttpHeaderFW header = beginEx.headers().matchFirst(h -> headerName.equals(h.name())); + if (header != null) + { + result = header.value().asString(); + } + return result; + } } diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/config/HttpQueryStringComparator.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/config/HttpQueryStringComparator.java new file mode 100644 index 0000000000..2e904069fd --- /dev/null +++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/config/HttpQueryStringComparator.java @@ -0,0 +1,99 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.http.internal.config; + +import java.util.Comparator; + +public class HttpQueryStringComparator implements Comparator +{ + @Override + public int compare( + String input1, + String input2) + { + int result = 0; + int index1 = 0; + int index2 = 0; + + while (hasNext(input1, index1) && hasNext(input2, index2)) + { + boolean percent1 = percent(input1, index1); + boolean percent2 = percent(input2, index2); + char char1 = charAt(input1, index1, percent1); + char char2 = charAt(input2, index2, percent2); + + if (char1 != char2) + { + result = Character.compare(char1, char2); + break; + } + + index1 = nextIndex(index1, percent1); + index2 = nextIndex(index2, percent2); + } + + return result == 0 ? Boolean.compare(hasNext(input1, index1), hasNext(input2, index2)) : result; + } + + private boolean hasNext( + String input, + int index) + { + return index < input.length(); + } + + private boolean percent( + String input, + int index) + { + return input.charAt(index) == '%' && index + 3 <= input.length(); + } + + private char charAt( + String input, + int index, + boolean percent) + { + char result; + if (percent) + { + char hexDigit1 = input.charAt(index + 1); + char hexDigit2 = input.charAt(index + 2); + result = toChar(hexDigit1, hexDigit2); + } + else + { + result = input.charAt(index); + } + return result; + } + + private char toChar( + char hexDigit1, + char hexDigit2) + { + int int1 = Character.digit(hexDigit1, 16); + int int2 = Character.digit(hexDigit2, 16); + return (char) ((int1 << 4) | int2); + } + + private int nextIndex( + int index, + boolean percent) + { + return index + (percent ? 3 : 1); + } +} diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/config/HttpRequestType.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/config/HttpRequestType.java new file mode 100644 index 0000000000..0b4386deca --- /dev/null +++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/config/HttpRequestType.java @@ -0,0 +1,146 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.http.internal.config; + +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.aklivity.zilla.runtime.binding.http.config.HttpRequestConfig; +import io.aklivity.zilla.runtime.binding.http.internal.types.String8FW; +import io.aklivity.zilla.runtime.engine.validator.Validator; + +public final class HttpRequestType +{ + private static final String PATH_FORMAT = "^%s/?(?:\\?.*)?$"; + private static final String PATH_REGEX = "\\{([a-zA-Z0-9_-]+)\\}"; + private static final String PATH_REPLACEMENT = "(?<$1>.+?)"; + private static final String QUERY_REGEX = "(?<=[?&])([^&=]+)=([^&]+)(?=&|$)"; + private static final Pattern QUERY_PATTERN = Pattern.compile(QUERY_REGEX); + private static final String EMPTY_INPUT = ""; + + // selectors + public final String path; + public final HttpRequestConfig.Method method; + public final List contentType; + + // matchers + public final Matcher pathMatcher; + public final Matcher queryMatcher; + + // validators + public final Map headers; + public final Map pathParams; + public final Map queryParams; + public final Validator content; + + private HttpRequestType( + String path, + HttpRequestConfig.Method method, + List contentType, + Matcher pathMatcher, + Matcher queryMatcher, + Map headers, + Map pathParams, + Map queryParams, + Validator content) + { + this.path = path; + this.method = method; + this.contentType = contentType; + this.pathMatcher = pathMatcher; + this.queryMatcher = queryMatcher; + this.headers = headers; + this.pathParams = pathParams; + this.queryParams = queryParams; + this.content = content; + } + + public static Builder builder() + { + return new Builder(); + } + + public static final class Builder + { + private String path; + private HttpRequestConfig.Method method; + private List contentType; + private Map headers; + private Map pathParams; + private Map queryParams; + private Validator content; + + public Builder path( + String path) + { + this.path = path; + return this; + } + + public Builder method( + HttpRequestConfig.Method method) + { + this.method = method; + return this; + } + + public Builder contentType( + List contentType) + { + this.contentType = contentType; + return this; + } + + public Builder headers( + Map headers) + { + this.headers = headers; + return this; + } + + public Builder pathParams( + Map pathParams) + { + this.pathParams = pathParams; + return this; + } + + public Builder queryParams( + Map queryParams) + { + this.queryParams = queryParams; + return this; + } + + public Builder content( + Validator content) + { + this.content = content; + return this; + } + + public HttpRequestType build() + { + String pathPattern = String.format(PATH_FORMAT, path.replaceAll(PATH_REGEX, PATH_REPLACEMENT)); + Matcher pathMatcher = Pattern.compile(pathPattern).matcher(EMPTY_INPUT); + Matcher queryMatcher = QUERY_PATTERN.matcher(EMPTY_INPUT); + return new HttpRequestType(path, method, contentType, pathMatcher, queryMatcher, headers, pathParams, queryParams, + content); + } + } +} diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/stream/HttpServerFactory.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/stream/HttpServerFactory.java index d30efeeee2..c713fb5374 100644 --- a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/stream/HttpServerFactory.java +++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/stream/HttpServerFactory.java @@ -57,11 +57,13 @@ import java.util.Set; import java.util.SortedSet; import java.util.function.BiConsumer; +import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.LongFunction; import java.util.function.LongSupplier; import java.util.function.LongUnaryOperator; +import java.util.function.ToLongFunction; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -100,6 +102,7 @@ import io.aklivity.zilla.runtime.binding.http.internal.codec.Http2SettingsFW; import io.aklivity.zilla.runtime.binding.http.internal.codec.Http2WindowUpdateFW; import io.aklivity.zilla.runtime.binding.http.internal.config.HttpBindingConfig; +import io.aklivity.zilla.runtime.binding.http.internal.config.HttpRequestType; import io.aklivity.zilla.runtime.binding.http.internal.config.HttpRouteConfig; import io.aklivity.zilla.runtime.binding.http.internal.hpack.HpackContext; import io.aklivity.zilla.runtime.binding.http.internal.hpack.HpackHeaderBlockFW; @@ -139,7 +142,9 @@ import io.aklivity.zilla.runtime.engine.buffer.BufferPool; import io.aklivity.zilla.runtime.engine.concurrent.Signaler; import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.ValidatorConfig; import io.aklivity.zilla.runtime.engine.guard.GuardHandler; +import io.aklivity.zilla.runtime.engine.validator.Validator; public final class HttpServerFactory implements HttpStreamFactory { @@ -401,6 +406,7 @@ public final class HttpServerFactory implements HttpStreamFactory private final Array32FW headers400; private final Array32FW headers403; private final Array32FW headers404; + private final DirectBuffer response400; private final DirectBuffer response403; private final DirectBuffer response404; @@ -494,6 +500,8 @@ public final class HttpServerFactory implements HttpStreamFactory private final Http2ServerDecoder decodeHttp2IgnoreAll = this::decodeHttp2IgnoreAll; private final EnumMap decodersByFrameType; + private final BiFunction, Validator> createValidator; + { final EnumMap decodersByFrameType = new EnumMap<>(Http2FrameType.class); decodersByFrameType.put(Http2FrameType.SETTINGS, decodeHttp2Settings); @@ -566,6 +574,7 @@ public HttpServerFactory( this.connectionClose = CONNECTION_CLOSE_PATTERN.matcher(""); this.maximumHeadersSize = bufferPool.slotCapacity(); this.decodeMax = bufferPool.slotCapacity(); + this.createValidator = context::createValidator; this.encodeMax = bufferPool.slotCapacity(); this.bindings = new Long2ObjectHashMap<>(); @@ -574,6 +583,7 @@ public HttpServerFactory( this.headers400 = initHeadersEmpty(config, STATUS_400); this.headers403 = initHeaders(config, STATUS_403); this.headers404 = initHeadersEmpty(config, STATUS_404); + this.response400 = initResponse(config, 400, "Bad Request"); this.response403 = initResponse(config, 403, "Forbidden"); this.response404 = initResponse(config, 404, "Not Found"); } @@ -588,7 +598,7 @@ public int routedTypeId() public void attach( BindingConfig binding) { - HttpBindingConfig httpBinding = new HttpBindingConfig(binding); + HttpBindingConfig httpBinding = new HttpBindingConfig(binding, createValidator); bindings.put(binding.id, httpBinding); } @@ -1066,7 +1076,13 @@ else if (!isCorsRequestAllowed(server.binding, headers)) HttpPolicyConfig policy = binding.access().effectivePolicy(headers); final String origin = policy == CROSS_ORIGIN ? headers.get(HEADER_NAME_ORIGIN) : null; - server.onDecodeHeaders(server.routedId, route.id, traceId, exchangeAuth, policy, origin, beginEx); + server.requestType = binding.resolveRequestType(beginEx); + boolean headersValid = server.onDecodeHeaders(server.routedId, route.id, traceId, exchangeAuth, + policy, origin, beginEx); + if (!headersValid) + { + error = response400; + } } else { @@ -1567,6 +1583,7 @@ private final class HttpServer private long replyAck; private long replyBudgetId; private int replyMax; + private HttpRequestType requestType; private HttpServer( HttpBindingConfig binding, @@ -2116,6 +2133,30 @@ private void decodeNetwork( } } + private void onDecodeBodyInvalid( + long traceId, + long authorization, + DirectBuffer error) + { + HttpExchangeState responseState = exchange.responseState; + exchange.doRequestAbort(traceId, EMPTY_OCTETS); + exchange.doResponseReset(traceId); + + if (responseState != HttpExchangeState.OPEN) + { + replyCloseOnFlush = true; + doNetworkData(traceId, authorization, 0L, error.capacity() + replyPad, error, 0, error.capacity()); + if (encodeSlot == NO_SLOT) + { + doNetworkEnd(traceId, authorization); + } + } + else + { + doNetworkAbort(traceId, authorization); + } + } + private void onDecodeHeadersError( long traceId, long authorization, @@ -2205,7 +2246,7 @@ private void onDecodeCorsPreflight( assert exchange == null; } - private void onDecodeHeaders( + private boolean onDecodeHeaders( long originId, long routedId, long traceId, @@ -2214,14 +2255,19 @@ private void onDecodeHeaders( String origin, HttpBeginExFW beginEx) { - final HttpExchange exchange = new HttpExchange(originId, routedId, authorization, traceId, policy, origin); - exchange.doRequestBegin(traceId, beginEx); - exchange.doResponseWindow(traceId); + boolean headersValid = binding.validateHeaders(requestType, beginEx); + if (headersValid) + { + final HttpExchange exchange = new HttpExchange(originId, routedId, authorization, traceId, policy, origin); + exchange.doRequestBegin(traceId, beginEx); + exchange.doResponseWindow(traceId); - final HttpHeaderFW connection = beginEx.headers().matchFirst(h -> HEADER_CONNECTION.equals(h.name())); - exchange.responseClosing = connection != null && connectionClose.reset(connection.value().asString()).matches(); + final HttpHeaderFW connection = beginEx.headers().matchFirst(h -> HEADER_CONNECTION.equals(h.name())); + exchange.responseClosing = connection != null && connectionClose.reset(connection.value().asString()).matches(); - this.exchange = exchange; + this.exchange = exchange; + } + return headersValid; } private void onDecodeHeadersOnly( @@ -2244,7 +2290,18 @@ private int onDecodeBody( int limit, Flyweight extension) { - return exchange.doRequestData(traceId, budgetId, buffer, offset, limit, extension); + boolean contentValid = binding.validateContent(requestType, buffer, 0, limit - offset); + int result; + if (contentValid) + { + result = exchange.doRequestData(traceId, budgetId, buffer, offset, limit, extension); + } + else + { + onDecodeBodyInvalid(traceId, authorization, ERROR_400_BAD_REQUEST); + result = limit; + } + return result; } private void onDecodeTrailers( @@ -4833,10 +4890,6 @@ else if (!isCorsRequestAllowed(binding, headers)) HttpPolicyConfig policy = binding.access().effectivePolicy(headers); final String origin = policy == CROSS_ORIGIN ? headers.get(HEADER_NAME_ORIGIN) : null; - final Http2Exchange exchange = - new Http2Exchange(originId, routedId, NO_REQUEST_ID, streamId, exchangeAuth, - traceId, policy, origin, contentLength); - if (binding.options != null && binding.options.overrides != null) { binding.options.overrides.forEach((k, v) -> headers.put(k.asString(), v.asString())); @@ -4847,11 +4900,23 @@ else if (!isCorsRequestAllowed(binding, headers)) .headers(hs -> headers.forEach((n, v) -> hs.item(h -> h.name(n).value(v)))) .build(); - exchange.doRequestBegin(traceId, beginEx); + HttpRequestType requestType = binding.resolveRequestType(beginEx); + + final Http2Exchange exchange = new Http2Exchange(originId, routedId, NO_REQUEST_ID, streamId, + exchangeAuth, traceId, policy, origin, contentLength, requestType); - if (endRequest) + boolean headersValid = binding.validateHeaders(requestType, beginEx); + if (headersValid) { - exchange.doRequestEnd(traceId, EMPTY_OCTETS); + exchange.doRequestBegin(traceId, beginEx); + if (endRequest) + { + exchange.doRequestEnd(traceId, EMPTY_OCTETS); + } + } + else + { + doEncodeHeaders(traceId, authorization, streamId, headers400, true); } } } @@ -5054,25 +5119,40 @@ private int onDecodeData( else { final int payloadLength = payload.capacity(); - - if (payloadLength > 0) + boolean contentValid = binding.validateContent(exchange.request, payload, 0, payloadLength); + if (contentValid) { - payloadRemaining.set(payloadLength); - exchange.doRequestData(traceId, payload, payloadRemaining); - progress += payloadLength - payloadRemaining.value; - deferred += payloadRemaining.value; - } + if (payloadLength > 0) + { + payloadRemaining.set(payloadLength); + exchange.doRequestData(traceId, payload, payloadRemaining); + progress += payloadLength - payloadRemaining.value; + deferred += payloadRemaining.value; + } - if (deferred == 0 && Http2Flags.endStream(flags)) - { - if (exchange.requestContentLength != -1 && exchange.contentObserved != exchange.requestContentLength) + if (deferred == 0 && Http2Flags.endStream(flags)) { - doEncodeRstStream(traceId, streamId, Http2ErrorCode.PROTOCOL_ERROR); + if (exchange.requestContentLength != -1 && + exchange.contentObserved != exchange.requestContentLength) + { + doEncodeRstStream(traceId, streamId, Http2ErrorCode.PROTOCOL_ERROR); + } + else + { + exchange.doRequestEnd(traceId, EMPTY_OCTETS); + } } - else + } + else + { + if (!HttpState.replyOpened(exchange.state)) { - exchange.doRequestEnd(traceId, EMPTY_OCTETS); + doEncodeHeaders(traceId, authorization, streamId, headers400, true); } + doEncodeRstStream(traceId, streamId, Http2ErrorCode.CANCEL); + exchange.doRequestAbort(traceId, EMPTY_OCTETS); + exchange.doResponseReset(traceId); + progress += payloadLength; } } } @@ -5206,7 +5286,7 @@ private void doEncodePromise( doEncodePushPromise(traceId, authorization, pushId, promiseId, promise); final Http2Exchange exchange = new Http2Exchange(originId, routedId, requestId, promiseId, - exchangeAuth, traceId, policy, origin, contentLength); + exchangeAuth, traceId, policy, origin, contentLength, null); final HttpBeginExFW beginEx = beginExRW.wrap(extBuffer, 0, extBuffer.capacity()) .typeId(httpTypeId) @@ -5522,6 +5602,8 @@ private final class Http2Exchange private long responseAck; private int responseMax; + private final HttpRequestType request; + private Http2Exchange( long originId, long routedId, @@ -5531,7 +5613,8 @@ private Http2Exchange( long traceId, HttpPolicyConfig policy, String origin, - long requestContentLength) + long requestContentLength, + HttpRequestType request) { this.originId = originId; this.routedId = routedId; @@ -5543,6 +5626,7 @@ private Http2Exchange( this.requestId = requestId == NO_REQUEST_ID ? supplyInitialId.applyAsLong(routedId) : requestId; this.responseId = supplyReplyId.applyAsLong(this.requestId); this.expiringId = expireIfNecessary(guard, sessionId, originId, routedId, replyId, traceId, streamId); + this.request = request; } private int initialWindow() @@ -5573,6 +5657,7 @@ private void doRequestData( MutableInteger remaining) { assert HttpState.initialOpening(state); + assert !HttpState.initialClosing(state); if (localBudget < remaining.value) { @@ -5611,6 +5696,8 @@ private void doRequestEnd( long traceId, Flyweight extension) { + assert !HttpState.initialClosing(state); + if (!HttpState.initialOpened(state)) { state = HttpState.closingInitial(state); diff --git a/runtime/binding-http/src/test/java/io/aklivity/zilla/runtime/binding/http/internal/config/HttpQueryStringComparatorTest.java b/runtime/binding-http/src/test/java/io/aklivity/zilla/runtime/binding/http/internal/config/HttpQueryStringComparatorTest.java new file mode 100644 index 0000000000..aeebd13bcd --- /dev/null +++ b/runtime/binding-http/src/test/java/io/aklivity/zilla/runtime/binding/http/internal/config/HttpQueryStringComparatorTest.java @@ -0,0 +1,146 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.http.internal.config; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.comparator.ComparatorMatcherBuilder.comparedBy; + +import org.junit.Test; + +public class HttpQueryStringComparatorTest +{ + private final HttpQueryStringComparator comparator = new HttpQueryStringComparator(); + + @Test + public void shouldCompareSameUnencodedEqualsToUnencoded() + { + assertThat("hello", comparedBy(comparator).comparesEqualTo("hello")); + } + + @Test + public void shouldCompareSameUnencodedEqualsToEncoded() + { + assertThat("hello", comparedBy(comparator).comparesEqualTo("%68%65%6C%6C%6F")); + } + + @Test + public void shouldCompareSameEncodedEqualsToUnencoded() + { + assertThat("%68%65%6C%6C%6F", comparedBy(comparator).comparesEqualTo("hello")); + } + + @Test + public void shouldCompareSameEncodedEqualsToEncoded() + { + assertThat("%68%65%6c%6c%6f", comparedBy(comparator).comparesEqualTo("%68%65%6C%6C%6F")); + } + + @Test + public void shouldCompareSmallerUnencodedLessThanLargerUnencoded() + { + assertThat("ciao", comparedBy(comparator).lessThan("hello")); + } + + @Test + public void shouldCompareSmallerUnencodedLessThanLargerEncoded() + { + assertThat("ciao", comparedBy(comparator).lessThan("%68%65%6C%6C%6F")); + } + + @Test + public void shouldCompareSmallerEncodedLessThanLargerUnencoded() + { + assertThat("%63%69%61%6F", comparedBy(comparator).lessThan("hello")); + } + + @Test + public void shouldCompareSmallerEncodedLessThanLargerEncoded() + { + assertThat("%63%69%61%6F", comparedBy(comparator).lessThan("%68%65%6c%6c%6f")); + } + + @Test + public void shouldCompareShorterUnencodedLessThanLongerUnencoded() + { + assertThat("hello", comparedBy(comparator).lessThan("hello1")); + } + + @Test + public void shouldCompareShorterUnencodedLessThanLongerEncoded() + { + assertThat("hello", comparedBy(comparator).lessThan("%68%65%6C%6C%6F%49")); + } + + @Test + public void shouldCompareShorterEncodedLessThanLongerUnencoded() + { + assertThat("%68%65%6C%6C%6F", comparedBy(comparator).lessThan("hello1")); + } + + @Test + public void shouldCompareShorterEncodedLessThanLongerEncoded() + { + assertThat("%68%65%6C%6C%6F", comparedBy(comparator).lessThan("%68%65%6C%6C%6F%49")); + } + + @Test + public void shouldCompareLargerUnencodedGreaterThanSmallerUnencoded() + { + assertThat("hello", comparedBy(comparator).greaterThan("ciao")); + } + + @Test + public void shouldCompareLargerUnencodedGreaterThanSmallerEncoded() + { + assertThat("hello", comparedBy(comparator).greaterThan("%63%69%61%6F")); + } + + @Test + public void shouldCompareLargerEncodedGreaterThanSmallerUnencoded() + { + assertThat("%68%65%6C%6C%6F", comparedBy(comparator).greaterThan("ciao")); + } + + @Test + public void shouldCompareLargerEncodedGreaterThanSmallerEncoded() + { + assertThat("%68%65%6C%6C%6F", comparedBy(comparator).greaterThan("%63%69%61%6F")); + } + + @Test + public void shouldCompareLongerUnencodedGreaterThanShorterUnencoded() + { + assertThat("hello1", comparedBy(comparator).greaterThan("hello")); + } + + @Test + public void shouldCompareLongerUnencodedGreaterThanShorterEncoded() + { + assertThat("hello1", comparedBy(comparator).greaterThan("%68%65%6C%6C%6F")); + } + + @Test + public void shouldCompareLongerEncodedGreaterThanShorterUnencoded() + { + assertThat("%68%65%6C%6C%6F%49", comparedBy(comparator).greaterThan("hello")); + } + + @Test + public void shouldCompareLongerEncodedGreaterThanShorterEncoded() + { + assertThat("%68%65%6C%6C%6F%49", comparedBy(comparator).greaterThan("%68%65%6C%6C%6F")); + } +} diff --git a/runtime/binding-http/src/test/java/io/aklivity/zilla/runtime/binding/http/internal/config/HttpRequestTypeTest.java b/runtime/binding-http/src/test/java/io/aklivity/zilla/runtime/binding/http/internal/config/HttpRequestTypeTest.java new file mode 100644 index 0000000000..853c3db17a --- /dev/null +++ b/runtime/binding-http/src/test/java/io/aklivity/zilla/runtime/binding/http/internal/config/HttpRequestTypeTest.java @@ -0,0 +1,277 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.http.internal.config; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +import java.util.regex.Matcher; + +import org.junit.Test; + +public class HttpRequestTypeTest +{ + @Test + public void shouldParsePath() + { + // GIVEN + String configPath = "/valid/{category}/{id}"; + String actualPath = "/valid/cat/garfield"; + HttpRequestType requestType = HttpRequestType.builder() + .path(configPath) + .build(); + + // WHEN + Matcher pathMatcher = requestType.pathMatcher.reset(actualPath); + Matcher queryMatcher = requestType.queryMatcher.reset(actualPath); + + // THEN + boolean pathMatches = pathMatcher.matches(); + assertThat(pathMatches, equalTo(true)); + assertThat(pathMatcher.group("category"), equalTo("cat")); + assertThat(pathMatcher.group("id"), equalTo("garfield")); + + boolean queryFound = queryMatcher.find(); + assertThat(queryFound, equalTo(false)); + } + + @Test + public void shouldParsePathWithSlash() + { + // GIVEN + String configPath = "/valid/{category}/{id}"; + String actualPath = "/valid/cat/garfield/"; + HttpRequestType requestType = HttpRequestType.builder() + .path(configPath) + .build(); + + // WHEN + Matcher pathMatcher = requestType.pathMatcher.reset(actualPath); + Matcher queryMatcher = requestType.queryMatcher.reset(actualPath); + + // THEN + boolean pathMatches = pathMatcher.matches(); + assertThat(pathMatches, equalTo(true)); + assertThat(pathMatcher.group("category"), equalTo("cat")); + assertThat(pathMatcher.group("id"), equalTo("garfield")); + + boolean queryFound = queryMatcher.find(); + assertThat(queryFound, equalTo(false)); + } + + @Test + public void shouldParsePathWithQuestionMark() + { + // GIVEN + String configPath = "/valid/{category}/{id}"; + String actualPath = "/valid/cat/garfield?"; + HttpRequestType requestType = HttpRequestType.builder() + .path(configPath) + .build(); + + // WHEN + Matcher pathMatcher = requestType.pathMatcher.reset(actualPath); + Matcher queryMatcher = requestType.queryMatcher.reset(actualPath); + + // THEN + boolean pathMatches = pathMatcher.matches(); + assertThat(pathMatches, equalTo(true)); + assertThat(pathMatcher.group("category"), equalTo("cat")); + assertThat(pathMatcher.group("id"), equalTo("garfield")); + + boolean queryFound = queryMatcher.find(); + assertThat(queryFound, equalTo(false)); + } + + @Test + public void shouldParsePathWithSlashAndQuestionMark() + { + // GIVEN + String configPath = "/valid/{category}/{id}"; + String actualPath = "/valid/cat/garfield/?"; + HttpRequestType requestType = HttpRequestType.builder() + .path(configPath) + .build(); + + // WHEN + Matcher pathMatcher = requestType.pathMatcher.reset(actualPath); + Matcher queryMatcher = requestType.queryMatcher.reset(actualPath); + + // THEN + boolean pathMatches = pathMatcher.matches(); + assertThat(pathMatches, equalTo(true)); + assertThat(pathMatcher.group("category"), equalTo("cat")); + assertThat(pathMatcher.group("id"), equalTo("garfield")); + + boolean queryFound = queryMatcher.find(); + assertThat(queryFound, equalTo(false)); + } + + @Test + public void shouldParsePathAndQuery() + { + // GIVEN + String configPath = "/valid/{category}/{id}"; + String actualPath = "/valid/cat/garfield?hello=ciao&day=nap&answer=42"; + HttpRequestType requestType = HttpRequestType.builder() + .path(configPath) + .build(); + + // WHEN + Matcher pathMatcher = requestType.pathMatcher.reset(actualPath); + Matcher queryMatcher = requestType.queryMatcher.reset(actualPath); + + // THEN + boolean pathMatches = pathMatcher.matches(); + assertThat(pathMatches, equalTo(true)); + assertThat(pathMatcher.group("category"), equalTo("cat")); + assertThat(pathMatcher.group("id"), equalTo("garfield")); + + boolean queryFound = queryMatcher.find(); + assertThat(queryFound, equalTo(true)); + assertThat(queryMatcher.group(1), equalTo("hello")); + assertThat(queryMatcher.group(2), equalTo("ciao")); + + queryFound = queryMatcher.find(); + assertThat(queryFound, equalTo(true)); + assertThat(queryMatcher.group(1), equalTo("day")); + assertThat(queryMatcher.group(2), equalTo("nap")); + + queryFound = queryMatcher.find(); + assertThat(queryFound, equalTo(true)); + assertThat(queryMatcher.group(1), equalTo("answer")); + assertThat(queryMatcher.group(2), equalTo("42")); + + queryFound = queryMatcher.find(); + assertThat(queryFound, equalTo(false)); + } + + @Test + public void shouldParsePathWithSlashAndQuery() + { + // GIVEN + String configPath = "/valid/{category}/{id}"; + String actualPath = "/valid/cat/garfield/?answer=42"; + HttpRequestType requestType = HttpRequestType.builder() + .path(configPath) + .build(); + + // WHEN + Matcher pathMatcher = requestType.pathMatcher.reset(actualPath); + Matcher queryMatcher = requestType.queryMatcher.reset(actualPath); + + // THEN + boolean pathMatches = pathMatcher.matches(); + assertThat(pathMatches, equalTo(true)); + assertThat(pathMatcher.group("category"), equalTo("cat")); + assertThat(pathMatcher.group("id"), equalTo("garfield")); + + boolean queryFound = queryMatcher.find(); + assertThat(queryFound, equalTo(true)); + assertThat(queryMatcher.group(1), equalTo("answer")); + assertThat(queryMatcher.group(2), equalTo("42")); + + queryFound = queryMatcher.find(); + assertThat(queryFound, equalTo(false)); + } + + @Test + public void shouldParsePathAndQueryWithAmpersand() + { + // GIVEN + String configPath = "/valid/{category}/{id}"; + String actualPath = "/valid/cat/garfield/?answer=42&"; + HttpRequestType requestType = HttpRequestType.builder() + .path(configPath) + .build(); + + // WHEN + Matcher pathMatcher = requestType.pathMatcher.reset(actualPath); + Matcher queryMatcher = requestType.queryMatcher.reset(actualPath); + + // THEN + boolean pathMatches = pathMatcher.matches(); + assertThat(pathMatches, equalTo(true)); + assertThat(pathMatcher.group("category"), equalTo("cat")); + assertThat(pathMatcher.group("id"), equalTo("garfield")); + + boolean queryFound = queryMatcher.find(); + assertThat(queryFound, equalTo(true)); + assertThat(queryMatcher.group(1), equalTo("answer")); + assertThat(queryMatcher.group(2), equalTo("42")); + + queryFound = queryMatcher.find(); + assertThat(queryFound, equalTo(false)); + } + + @Test + public void shouldParsePathAndIncompleteQuery1() + { + // GIVEN + String configPath = "/valid/{category}/{id}"; + String actualPath = "/valid/cat/garfield/?hello=ciao&answer="; + HttpRequestType requestType = HttpRequestType.builder() + .path(configPath) + .build(); + + // WHEN + Matcher pathMatcher = requestType.pathMatcher.reset(actualPath); + Matcher queryMatcher = requestType.queryMatcher.reset(actualPath); + + // THEN + boolean pathMatches = pathMatcher.matches(); + assertThat(pathMatches, equalTo(true)); + assertThat(pathMatcher.group("category"), equalTo("cat")); + assertThat(pathMatcher.group("id"), equalTo("garfield")); + + boolean queryFound = queryMatcher.find(); + assertThat(queryFound, equalTo(true)); + assertThat(queryMatcher.group(1), equalTo("hello")); + assertThat(queryMatcher.group(2), equalTo("ciao")); + + queryFound = queryMatcher.find(); + assertThat(queryFound, equalTo(false)); + } + + @Test + public void shouldParsePathAndIncompleteQuery2() + { + // GIVEN + String configPath = "/valid/{category}/{id}"; + String actualPath = "/valid/cat/garfield/?hello=ciao&answer"; + HttpRequestType requestType = HttpRequestType.builder() + .path(configPath) + .build(); + + // WHEN + Matcher pathMatcher = requestType.pathMatcher.reset(actualPath); + Matcher queryMatcher = requestType.queryMatcher.reset(actualPath); + + // THEN + boolean pathMatches = pathMatcher.matches(); + assertThat(pathMatches, equalTo(true)); + assertThat(pathMatcher.group("category"), equalTo("cat")); + assertThat(pathMatcher.group("id"), equalTo("garfield")); + + boolean queryFound = queryMatcher.find(); + assertThat(queryFound, equalTo(true)); + assertThat(queryMatcher.group(1), equalTo("hello")); + assertThat(queryMatcher.group(2), equalTo("ciao")); + + queryFound = queryMatcher.find(); + assertThat(queryFound, equalTo(false)); + } +} diff --git a/runtime/binding-http/src/test/java/io/aklivity/zilla/runtime/binding/http/internal/streams/rfc7230/server/ValidationIT.java b/runtime/binding-http/src/test/java/io/aklivity/zilla/runtime/binding/http/internal/streams/rfc7230/server/ValidationIT.java new file mode 100644 index 0000000000..831b5e9c02 --- /dev/null +++ b/runtime/binding-http/src/test/java/io/aklivity/zilla/runtime/binding/http/internal/streams/rfc7230/server/ValidationIT.java @@ -0,0 +1,71 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.http.internal.streams.rfc7230.server; + +import static io.aklivity.zilla.runtime.binding.http.internal.HttpConfiguration.HTTP_CONCURRENT_STREAMS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; + +public class ValidationIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("net", "io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/validation") + .addScriptRoot("app", "io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/validation"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configurationRoot("io/aklivity/zilla/specs/binding/http/config/v1.1") + .configure(HTTP_CONCURRENT_STREAMS, 100) + .external("app0") + .clean(); + + @Rule + public final TestRule chain = outerRule(engine).around(k3po).around(timeout); + + @Test + @Configuration("server.validation.yaml") + @Specification({ + "${net}/invalid/client", + "${app}/invalid/server" }) + public void shouldRejectInvalidRequests() throws Exception + { + k3po.finish(); + } + + @Test + @Configuration("server.validation.yaml") + @Specification({ + "${net}/valid/client", + "${app}/valid/server" }) + public void shouldProcessValidRequests() throws Exception + { + k3po.finish(); + } +} diff --git a/runtime/binding-http/src/test/java/io/aklivity/zilla/runtime/binding/http/internal/streams/rfc7540/server/ValidationIT.java b/runtime/binding-http/src/test/java/io/aklivity/zilla/runtime/binding/http/internal/streams/rfc7540/server/ValidationIT.java new file mode 100644 index 0000000000..1d82bc0d13 --- /dev/null +++ b/runtime/binding-http/src/test/java/io/aklivity/zilla/runtime/binding/http/internal/streams/rfc7540/server/ValidationIT.java @@ -0,0 +1,71 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.http.internal.streams.rfc7540.server; + +import static io.aklivity.zilla.runtime.binding.http.internal.HttpConfiguration.HTTP_CONCURRENT_STREAMS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; + +public class ValidationIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("net", "io/aklivity/zilla/specs/binding/http/streams/network/rfc7540/validation") + .addScriptRoot("app", "io/aklivity/zilla/specs/binding/http/streams/application/rfc7540/validation"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configurationRoot("io/aklivity/zilla/specs/binding/http/config/v2") + .configure(HTTP_CONCURRENT_STREAMS, 100) + .external("app0") + .clean(); + + @Rule + public final TestRule chain = outerRule(engine).around(k3po).around(timeout); + + @Test + @Configuration("server.validation.yaml") + @Specification({ + "${net}/invalid/client", + "${app}/invalid/server" }) + public void shouldRejectInvalidRequests() throws Exception + { + k3po.finish(); + } + + @Test + @Configuration("server.validation.yaml") + @Specification({ + "${net}/valid/client", + "${app}/valid/server" }) + public void shouldProcessValidRequests() throws Exception + { + k3po.finish(); + } +} diff --git a/runtime/binding-kafka-grpc/pom.xml b/runtime/binding-kafka-grpc/pom.xml index dd17e611a6..c9ea512e25 100644 --- a/runtime/binding-kafka-grpc/pom.xml +++ b/runtime/binding-kafka-grpc/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/binding-kafka/pom.xml b/runtime/binding-kafka/pom.xml index a08acb8591..8829db813d 100644 --- a/runtime/binding-kafka/pom.xml +++ b/runtime/binding-kafka/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheClientProduceFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheClientProduceFactory.java index 6a9f4de614..0d93e0ad1c 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheClientProduceFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheClientProduceFactory.java @@ -21,8 +21,10 @@ import static io.aklivity.zilla.runtime.engine.concurrent.Signaler.NO_CANCEL_ID; import static java.lang.System.currentTimeMillis; import static java.lang.Thread.currentThread; +import static java.time.Instant.now; import static java.util.concurrent.TimeUnit.SECONDS; +import java.nio.ByteBuffer; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.LongFunction; @@ -86,11 +88,15 @@ public final class KafkaCacheClientProduceFactory implements BindingHandler { private static final OctetsFW EMPTY_OCTETS = new OctetsFW().wrap(new UnsafeBuffer(), 0, 0); + private static final KafkaKeyFW EMPTY_KEY = + new OctetsFW().wrap(new UnsafeBuffer(ByteBuffer.wrap(new byte[] { 0x00 })), 0, 1) + .get(new KafkaKeyFW()::wrap); private static final Consumer EMPTY_EXTENSION = ex -> {}; private static final Array32FW EMPTY_TRAILERS = new Array32FW.Builder<>(new KafkaHeaderFW.Builder(), new KafkaHeaderFW()) .wrap(new UnsafeBuffer(new byte[8]), 0, 8) .build(); + private static final int PRODUCE_FLUSH_SEQUENCE = -1; private static final int ERROR_NOT_LEADER_FOR_PARTITION = 6; private static final int ERROR_RECORD_LIST_TOO_LARGE = 18; @@ -762,6 +768,47 @@ private void onClientInitialData( creditor.credit(traceId, partitionIndex, reserved); } + private void onClientInitialFlush( + KafkaCacheClientProduceStream stream, + FlushFW flush) + { + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + + stream.segment = partition.newHeadIfNecessary(partitionOffset, EMPTY_KEY, 0, 0); + + int error = NO_ERROR; + if (stream.segment != null) + { + final long nextOffset = partition.nextOffset(defaultOffset); + assert partitionOffset >= 0 && partitionOffset >= nextOffset + : String.format("%d >= 0 && %d >= %d", partitionOffset, partitionOffset, nextOffset); + + final long keyHash = partition.computeKeyHash(EMPTY_KEY); + partition.writeProduceEntryStart(partitionOffset, stream.segment, stream.entryMark, stream.position, + now().toEpochMilli(), stream.initialId, PRODUCE_FLUSH_SEQUENCE, + KafkaAckMode.LEADER_ONLY, EMPTY_KEY, keyHash, 0, EMPTY_TRAILERS, trailersSizeMax); + stream.partitionOffset = partitionOffset; + partitionOffset++; + + Array32FW trailers = EMPTY_TRAILERS; + + partition.writeProduceEntryFin(stream.segment, stream.entryMark, stream.position, stream.initialSeq, trailers); + flushClientFanInitialIfNecessary(traceId); + } + else + { + error = ERROR_RECORD_LIST_TOO_LARGE; + } + + if (error != NO_ERROR) + { + stream.cleanupClient(traceId, error); + onClientFanMemberClosed(traceId, stream); + } + creditor.credit(traceId, partitionIndex, reserved); + } + private void flushClientFanInitialIfNecessary( long traceId) { @@ -1314,12 +1361,25 @@ private void onClientInitialFlush( assert acknowledge <= sequence; assert sequence >= initialSeq; - initialSeq = sequence + reserved; + if (reserved > 0) + { + initialSeq = sequence + reserved; - assert initialAck <= initialSeq; + assert initialAck <= initialSeq; - final int noAck = (int) (initialSeq - initialAck); - doClientInitialWindow(traceId, noAck, noAck + initialBudgetMax); + if (initialSeq > initialAck + initialMax) + { + doClientInitialResetIfNecessary(traceId, EMPTY_OCTETS); + doClientReplyAbortIfNecessary(traceId); + fan.onClientFanMemberClosed(traceId, this); + } + else + { + fan.onClientInitialFlush(this, flush); + } + final int noAck = (int) (initialSeq - initialAck); + doClientInitialWindow(traceId, noAck, initialBudgetMax); + } } private void onClientInitialEnd( diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaMergedFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaMergedFactory.java index ea06bf2a43..281cb62edd 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaMergedFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaMergedFactory.java @@ -1325,6 +1325,8 @@ private void onMergedInitialFlush( FlushFW flush) { final long traceId = flush.traceId(); + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); final OctetsFW extension = flush.extension(); final int reserved = flush.reserved(); final ExtensionFW flushEx = extension.get(extensionRO::tryWrap); @@ -1379,6 +1381,10 @@ private void onMergedInitialFlush( final KafkaUnmergedProduceStream producer = findProducePartitionLeader(nextPartitionId); assert producer != null; + + initialSeq = sequence + reserved; + assert initialAck <= initialSeq; + producer.doProduceInitialFlush(traceId, reserved, kafkaMergedFlushEx); } } diff --git a/runtime/binding-mqtt-kafka/pom.xml b/runtime/binding-mqtt-kafka/pom.xml index b147ed1d93..15d3d031ca 100644 --- a/runtime/binding-mqtt-kafka/pom.xml +++ b/runtime/binding-mqtt-kafka/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSessionFactory.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSessionFactory.java index 17479704a8..df13a92ef6 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSessionFactory.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSessionFactory.java @@ -25,7 +25,6 @@ import java.nio.charset.StandardCharsets; import java.util.Optional; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.LongFunction; import java.util.function.LongSupplier; import java.util.function.LongUnaryOperator; @@ -126,7 +125,6 @@ public class MqttKafkaSessionFactory implements MqttKafkaStreamFactory private static final int SIGNAL_CONNECT_WILL_STREAM = 2; private static final int SIGNAL_EXPIRE_SESSION = 3; private static final int SIZE_OF_UUID = 38; - private static final AtomicInteger CONTEXT_COUNTER = new AtomicInteger(0); private static final int RETAIN_AVAILABLE_MASK = 1 << MqttServerCapabilities.RETAIN.value(); private static final int WILDCARD_AVAILABLE_MASK = 1 << MqttServerCapabilities.WILDCARD.value(); private static final int SUBSCRIPTION_IDS_AVAILABLE_MASK = 1 << MqttServerCapabilities.SUBSCRIPTION_IDS.value(); @@ -209,6 +207,7 @@ public class MqttKafkaSessionFactory implements MqttKafkaStreamFactory private String serverRef; private int reconnectAttempt; + private int nextContextId; public MqttKafkaSessionFactory( MqttKafkaConfiguration config, @@ -960,6 +959,14 @@ private void doKafkaBegin( long affinity) { reconnectAttempt = 0; + replySeq = 0; + replyAck = 0; + if (decodeSlot != NO_SLOT) + { + bufferPool.release(decodeSlot); + decodeSlot = NO_SLOT; + decodeSlotOffset = 0; + } willFetchers.values().forEach(f -> f.cleanup(traceId, authorization)); willFetchers.clear(); @@ -1186,7 +1193,6 @@ else if (type.equals(EXPIRY_SIGNAL_NAME_OCTETS) && sessionExpiryIds.containsKey( final MqttSessionSignalFW sessionSignal = mqttSessionSignalRO.wrap(buffer, offset, limit); - byte[] bytes = new byte[sessionSignal.sizeof()]; switch (sessionSignal.kind()) { @@ -1213,7 +1219,7 @@ else if (type.equals(EXPIRY_SIGNAL_NAME_OCTETS) && sessionExpiryIds.containsKey( case MqttSessionSignalFW.KIND_EXPIRY: final MqttExpirySignalFW expirySignal = sessionSignal.expiry(); long expireAt = expirySignal.expireAt(); - final String16FW expiryClientId = expirySignal.clientId(); + final String16FW expiryClientId = new String16FW(expirySignal.clientId().asString()); if (expireAt == MqttTime.UNKNOWN.value()) { @@ -1224,7 +1230,7 @@ else if (type.equals(EXPIRY_SIGNAL_NAME_OCTETS) && sessionExpiryIds.containsKey( expireAt = supplyTime.getAsLong() + expirySignal.delay(); } - final int contextId = CONTEXT_COUNTER.incrementAndGet(); + final int contextId = nextContextId++; expiryClientIds.put(contextId, expiryClientId); final long signalId = diff --git a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSessionProxyIT.java b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSessionProxyIT.java index 350186e2c4..272167bab1 100644 --- a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSessionProxyIT.java +++ b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSessionProxyIT.java @@ -252,6 +252,15 @@ public void shouldDecodeSessionExpirySignalFragmented() throws Exception k3po.finish(); } + @Test + @Configuration("proxy.yaml") + @Specification({ + "${kafka}/session.expiry.after.signal.stream.restart/server"}) + public void shouldExpireSessionAfterSignalStreamRestart() throws Exception + { + k3po.finish(); + } + @Test @Configuration("proxy.yaml") @Specification({ diff --git a/runtime/binding-mqtt/pom.xml b/runtime/binding-mqtt/pom.xml index d8df6bcde3..67e9061834 100644 --- a/runtime/binding-mqtt/pom.xml +++ b/runtime/binding-mqtt/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/binding-proxy/pom.xml b/runtime/binding-proxy/pom.xml index 5b7b8e73d3..b11968e627 100644 --- a/runtime/binding-proxy/pom.xml +++ b/runtime/binding-proxy/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/binding-sse-kafka/pom.xml b/runtime/binding-sse-kafka/pom.xml index b25b3e0e6d..b16730b2ff 100644 --- a/runtime/binding-sse-kafka/pom.xml +++ b/runtime/binding-sse-kafka/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/binding-sse/pom.xml b/runtime/binding-sse/pom.xml index cc27443d98..c46b3153e1 100644 --- a/runtime/binding-sse/pom.xml +++ b/runtime/binding-sse/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/binding-tcp/pom.xml b/runtime/binding-tcp/pom.xml index 2487f1067a..f277e2b62a 100644 --- a/runtime/binding-tcp/pom.xml +++ b/runtime/binding-tcp/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/binding-tls/pom.xml b/runtime/binding-tls/pom.xml index 42a7ab6b18..d9bf79655b 100644 --- a/runtime/binding-tls/pom.xml +++ b/runtime/binding-tls/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/binding-ws/pom.xml b/runtime/binding-ws/pom.xml index e7bb4fa458..0a91653877 100644 --- a/runtime/binding-ws/pom.xml +++ b/runtime/binding-ws/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/command-metrics/pom.xml b/runtime/command-metrics/pom.xml index 9eda922a03..b8a1a5710b 100644 --- a/runtime/command-metrics/pom.xml +++ b/runtime/command-metrics/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/command-start/pom.xml b/runtime/command-start/pom.xml index 3480fe8ddf..65f0484b05 100644 --- a/runtime/command-start/pom.xml +++ b/runtime/command-start/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/command-stop/pom.xml b/runtime/command-stop/pom.xml index 47026881ea..38d0bcb9e9 100644 --- a/runtime/command-stop/pom.xml +++ b/runtime/command-stop/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/command/pom.xml b/runtime/command/pom.xml index 6b596bedcd..303b2025e6 100644 --- a/runtime/command/pom.xml +++ b/runtime/command/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/engine/pom.xml b/runtime/engine/pom.xml index d3ef4fb13c..79e0a9ca24 100644 --- a/runtime/engine/pom.xml +++ b/runtime/engine/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/exporter-prometheus/pom.xml b/runtime/exporter-prometheus/pom.xml index 88bef67d94..2598d59116 100644 --- a/runtime/exporter-prometheus/pom.xml +++ b/runtime/exporter-prometheus/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/guard-jwt/pom.xml b/runtime/guard-jwt/pom.xml index 99a443e2f6..76cae666fd 100644 --- a/runtime/guard-jwt/pom.xml +++ b/runtime/guard-jwt/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/metrics-grpc/pom.xml b/runtime/metrics-grpc/pom.xml index d64605b94e..d5d52d6186 100644 --- a/runtime/metrics-grpc/pom.xml +++ b/runtime/metrics-grpc/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/metrics-http/pom.xml b/runtime/metrics-http/pom.xml index 7557a36363..0d3ecfa335 100644 --- a/runtime/metrics-http/pom.xml +++ b/runtime/metrics-http/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/metrics-stream/pom.xml b/runtime/metrics-stream/pom.xml index 3294d7ac3e..a254ed5ba7 100644 --- a/runtime/metrics-stream/pom.xml +++ b/runtime/metrics-stream/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/pom.xml b/runtime/pom.xml index a9c4280e09..7f2f8047f8 100644 --- a/runtime/pom.xml +++ b/runtime/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla zilla - 0.9.57 + 0.9.58 ../pom.xml diff --git a/runtime/vault-filesystem/pom.xml b/runtime/vault-filesystem/pom.xml index 5b86590610..178a5362a0 100644 --- a/runtime/vault-filesystem/pom.xml +++ b/runtime/vault-filesystem/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/binding-echo.spec/pom.xml b/specs/binding-echo.spec/pom.xml index 575da131de..4d623552d9 100644 --- a/specs/binding-echo.spec/pom.xml +++ b/specs/binding-echo.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/binding-fan.spec/pom.xml b/specs/binding-fan.spec/pom.xml index 4667acdc9b..7c9434d5d1 100644 --- a/specs/binding-fan.spec/pom.xml +++ b/specs/binding-fan.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/binding-filesystem.spec/pom.xml b/specs/binding-filesystem.spec/pom.xml index 0981327833..3f05c6e0f9 100644 --- a/specs/binding-filesystem.spec/pom.xml +++ b/specs/binding-filesystem.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/binding-grpc-kafka.spec/pom.xml b/specs/binding-grpc-kafka.spec/pom.xml index 4f74b35e6a..1a2d91db28 100644 --- a/specs/binding-grpc-kafka.spec/pom.xml +++ b/specs/binding-grpc-kafka.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/binding-grpc.spec/pom.xml b/specs/binding-grpc.spec/pom.xml index 3e524410a4..5047166c46 100644 --- a/specs/binding-grpc.spec/pom.xml +++ b/specs/binding-grpc.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/binding-http-filesystem.spec/pom.xml b/specs/binding-http-filesystem.spec/pom.xml index bb77c07ccf..4dddf58c61 100644 --- a/specs/binding-http-filesystem.spec/pom.xml +++ b/specs/binding-http-filesystem.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/binding-http-kafka.spec/pom.xml b/specs/binding-http-kafka.spec/pom.xml index e1ef587305..30af4d5fcf 100644 --- a/specs/binding-http-kafka.spec/pom.xml +++ b/specs/binding-http-kafka.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/binding-http.spec/pom.xml b/specs/binding-http.spec/pom.xml index dbe9ddf345..89d21908f9 100644 --- a/specs/binding-http.spec/pom.xml +++ b/specs/binding-http.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.validation.yaml b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.validation.yaml new file mode 100644 index 0000000000..16956d2571 --- /dev/null +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.validation.yaml @@ -0,0 +1,48 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +bindings: + net0: + type: http + kind: server + options: + requests: + - path: /hello + method: GET + content: test + - path: /valid/{category}/{id} + method: POST + content-type: + - text/plain + headers: + code: test + params: + path: + category: test + id: test + query: + page: test + content: + type: test + versions: + - http/1.1 + routes: + - exit: app0 + when: + - headers: + :authority: localhost:8080 diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v2/server.validation.yaml b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v2/server.validation.yaml new file mode 100644 index 0000000000..a925071f02 --- /dev/null +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v2/server.validation.yaml @@ -0,0 +1,48 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +bindings: + net0: + type: http + kind: server + options: + requests: + - path: /hello + method: GET + content: test + - path: /valid/{category}/{id} + method: POST + content-type: + - text/plain + headers: + code: test + params: + path: + category: test + id: test + query: + page: test + content: + type: test + versions: + - h2 + routes: + - exit: app0 + when: + - headers: + :authority: localhost:8080 diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/schema/http.schema.patch.json b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/schema/http.schema.patch.json index ac880b03bf..b5fee8c511 100644 --- a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/schema/http.schema.patch.json +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/schema/http.schema.patch.json @@ -237,6 +237,114 @@ { "type": "string" } + }, + "requests": + { + "type": "array", + "items": + { + "type": "object", + "properties": + { + "path": + { + "type": "string" + }, + "method": + { + "type": "string", + "enum": + [ + "GET", + "PUT", + "POST", + "DELETE", + "OPTIONS", + "HEAD", + "PATCH", + "TRACE" + ] + }, + "content-type": + { + "type": "array", + "items": + { + "type": "string" + } + }, + "headers": + { + "type": "object", + "patternProperties": + { + "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": + { + "$ref": "#/$defs/validator/type" + } + } + }, + "params": + { + "type": "object", + "properties": + { + "path": + { + "type": "object", + "patternProperties": + { + "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": + { + "$ref": "#/$defs/validator/type" + } + } + }, + "query": + { + "type": "object", + "patternProperties": + { + "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": + { + "$ref": "#/$defs/validator/type" + } + } + } + }, + "additionalProperties": false + }, + "content": + { + "$ref": "#/$defs/validator/type" + } + }, + "anyOf": + [ + { + "required": + [ + "path", + "headers" + ] + }, + { + "required": + [ + "path", + "params" + ] + }, + { + "required": + [ + "path", + "content" + ] + } + ], + "additionalProperties": false + } } }, "additionalProperties": false diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/schema/http.with.validators.schema.patch.json b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/schema/http.with.validators.schema.patch.json deleted file mode 100644 index b5fee8c511..0000000000 --- a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/schema/http.with.validators.schema.patch.json +++ /dev/null @@ -1,400 +0,0 @@ -[ - { - "op": "add", - "path": "/$defs/binding/properties/type/enum/-", - "value": "http" - }, - { - "op": "add", - "path": "/$defs/binding/allOf/-", - "value": - { - "if": - { - "properties": - { - "type": - { - "const": "http" - } - } - }, - "then": - { - "properties": - { - "type": - { - "const": "http" - }, - "kind": - { - "enum": [ "server", "client" ] - }, - "vault": false, - "options": - { - "properties": - { - "versions": - { - "title": "Versions", - "type": "array", - "default": [ "http/1.1", "h2" ], - "items": - { - "title": "Version", - "type": "string", - "enum": [ "http/1.1", "h2" ] - } - }, - "access-control": - { - "title": "Access Control", - "type": "object", - "properties": - { - "policy": - { - "title": "Policy", - "type": "string" - } - }, - "oneOf": - [ - { - "properties": - { - "policy": - { - "const": "same-origin" - } - } - }, - { - "properties": - { - "policy": - { - "const": "cross-origin" - }, - "allow": - { - "title": "Allow", - "type": "object", - "properties": - { - "origins": - { - "title": "Origins", - "type": "array", - "items": - { - "type": "string" - } - }, - "methods": - { - "title": "Methods", - "type": "array", - "items": - { - "type": "string" - } - }, - "headers": - { - "title": "Headers", - "type": "array", - "items": - { - "type": "string" - } - }, - "credentials": - { - "title": "Credentials", - "type": "boolean" - } - }, - "additionalProperties": false - }, - "max-age": - { - "title": "Max Age", - "type": "number" - }, - "expose": - { - "title": "Expose", - "type": "object", - "properties": - { - "headers": - { - "title": "Headers", - "type": "array", - "items": - { - "type": "string" - } - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "authorization": - { - "title": "Authorizations", - "type": "object", - "patternProperties": - { - "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": - { - "title": "Authorization", - "type": "object", - "properties": - { - "credentials": - { - "title": "Credentials", - "type": "object", - "properties": - { - "cookies": - { - "title": "Cookies", - "type": "object", - "additionalProperties": - { - "type": "string", - "pattern": ".*\\{credentials\\}.*" - } - }, - "headers": - { - "title": "Headers", - "type": "object", - "additionalProperties": - { - "type": "string", - "pattern": ".*\\{credentials\\}.*" - } - }, - "query": - { - "title": "Query Parameters", - "type": "object", - "additionalProperties": - { - "type": "string", - "pattern": ".*\\{credentials\\}.*" - } - } - }, - "additionalProperties": false, - "anyOf": - [ - { - "required": - [ - "cookies" - ] - }, - { - "required": - [ - "headers" - ] - }, - { - "required": - [ - "query" - ] - } - ] - } - }, - "additionalProperties": false, - "required": - [ - "credentials" - ] - } - }, - "maxProperties": 1 - }, - "overrides": - { - "title": "Overrides", - "type": "object", - "additionalProperties" : - { - "type": "string" - } - }, - "requests": - { - "type": "array", - "items": - { - "type": "object", - "properties": - { - "path": - { - "type": "string" - }, - "method": - { - "type": "string", - "enum": - [ - "GET", - "PUT", - "POST", - "DELETE", - "OPTIONS", - "HEAD", - "PATCH", - "TRACE" - ] - }, - "content-type": - { - "type": "array", - "items": - { - "type": "string" - } - }, - "headers": - { - "type": "object", - "patternProperties": - { - "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": - { - "$ref": "#/$defs/validator/type" - } - } - }, - "params": - { - "type": "object", - "properties": - { - "path": - { - "type": "object", - "patternProperties": - { - "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": - { - "$ref": "#/$defs/validator/type" - } - } - }, - "query": - { - "type": "object", - "patternProperties": - { - "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": - { - "$ref": "#/$defs/validator/type" - } - } - } - }, - "additionalProperties": false - }, - "content": - { - "$ref": "#/$defs/validator/type" - } - }, - "anyOf": - [ - { - "required": - [ - "path", - "headers" - ] - }, - { - "required": - [ - "path", - "params" - ] - }, - { - "required": - [ - "path", - "content" - ] - } - ], - "additionalProperties": false - } - } - }, - "additionalProperties": false - }, - "routes": - { - "items": - { - "properties": - { - "when": - { - "items": - { - "properties": - { - "headers": - { - "title": "Headers", - "type": "object", - "additionalProperties" : - { - "type": "string" - } - } - }, - "additionalProperties": false - } - }, - "with": false - } - } - } - }, - "anyOf": - [ - { - "required": - [ - "exit" - ] - }, - { - "required": - [ - "routes" - ] - } - ] - } - } - } -] diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/validation/invalid/client.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/validation/invalid/client.rpt new file mode 100644 index 0000000000..872011685e --- /dev/null +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/validation/invalid/client.rpt @@ -0,0 +1,38 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +# Request 4 - invalid content + +# We receive a begin frame as the headers are valid +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123?page=1234567890123") + .header(":authority", "localhost:8080") + .header("code", "1234567890123") + .header("content-type", "text/plain") + .header("content-length", "3") + .build()} +connected + +# We receive an abort frame as the content is invalid +write abort +read abort diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/validation/invalid/server.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/validation/invalid/server.rpt new file mode 100644 index 0000000000..bdb39ddab1 --- /dev/null +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/validation/invalid/server.rpt @@ -0,0 +1,39 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +accept "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +# Request 4 - invalid content + +# We receive a begin frame as the headers are valid +read zilla:begin.ext ${http:matchBeginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123?page=1234567890123") + .header(":authority", "localhost:8080") + .header("code", "1234567890123") + .header("content-type", "text/plain") + .header("content-length", "3") + .build()} +connected + +# We receive an abort frame as the content is invalid +read aborted +write aborted diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/validation/valid/client.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/validation/valid/client.rpt new file mode 100644 index 0000000000..636f5ed0d5 --- /dev/null +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/validation/valid/client.rpt @@ -0,0 +1,155 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +# Request 1 - valid path params +connect "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123") + .header(":authority", "localhost:8080") + .build()} +connected + +write close + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-length", "9") + .build()} + +read "response1" +read closed +read notify RESPONSE_ONE_RECEIVED + + +# Request 2 - valid path params, query param +connect await RESPONSE_ONE_RECEIVED + "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123?page=1234567890123") + .header(":authority", "localhost:8080") + .build()} +connected + +write close + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-length", "9") + .build()} + +read "response2" +read closed +read notify RESPONSE_TWO_RECEIVED + + +# Request 3 - valid path params, url encoded query param +connect await RESPONSE_TWO_RECEIVED + "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123?%70%61%67%65=1234567890123") + .header(":authority", "localhost:8080") + .build()} +connected + +write close + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-length", "9") + .build()} + +read "response3" +read closed +read notify RESPONSE_THREE_RECEIVED + + +# Request 4 - valid path params, query param, header field +connect await RESPONSE_THREE_RECEIVED + "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123?page=1234567890123") + .header(":authority", "localhost:8080") + .header("code", "1234567890123") + .build()} +connected + +write close + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-length", "9") + .build()} + +read "response4" +read closed +read notify RESPONSE_FOUR_RECEIVED + + +# Request 5 - valid path params, query param, header field, valid content +connect await RESPONSE_FOUR_RECEIVED + "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123?page=1234567890123") + .header(":authority", "localhost:8080") + .header("code", "1234567890123") + .build()} +connected + +write "1234567890123" +write close + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-length", "9") + .build()} + +read "response5" +read closed diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/validation/valid/server.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/validation/valid/server.rpt new file mode 100644 index 0000000000..5cd1eaf09f --- /dev/null +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/validation/valid/server.rpt @@ -0,0 +1,137 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +accept "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +# Request 1 - valid path params +accepted +read zilla:begin.ext ${http:matchBeginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123") + .header(":authority", "localhost:8080") + .build()} +connected +read closed + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-length", "9") + .build()} +write flush + +write "response1" +write close + + +# Request 2 - valid path params, query param +accepted +read zilla:begin.ext ${http:matchBeginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123?page=1234567890123") + .header(":authority", "localhost:8080") + .build()} +connected +read closed + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-length", "9") + .build()} +write flush + +write "response2" +write close + + +# Request 3 - valid path params, url encoded query param +accepted +read zilla:begin.ext ${http:matchBeginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123?%70%61%67%65=1234567890123") + .header(":authority", "localhost:8080") + .build()} +connected +read closed + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-length", "9") + .build()} +write flush + +write "response3" +write close + + +# Request 4 - valid path params, query param, header field +accepted +read zilla:begin.ext ${http:matchBeginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123?page=1234567890123") + .header(":authority", "localhost:8080") + .header("code", "1234567890123") + .build()} +connected +read closed + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-length", "9") + .build()} +write flush + +write "response4" +write close + + +# Request 5 - valid path params, query param, header field, valid content +accepted +read zilla:begin.ext ${http:matchBeginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123?page=1234567890123") + .header(":authority", "localhost:8080") + .header("code", "1234567890123") + .build()} +connected + +read "1234567890123" +read closed + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-length", "9") + .build()} +write flush + +write "response5" +write close diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7540/validation/invalid/client.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7540/validation/invalid/client.rpt new file mode 100644 index 0000000000..872011685e --- /dev/null +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7540/validation/invalid/client.rpt @@ -0,0 +1,38 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +# Request 4 - invalid content + +# We receive a begin frame as the headers are valid +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123?page=1234567890123") + .header(":authority", "localhost:8080") + .header("code", "1234567890123") + .header("content-type", "text/plain") + .header("content-length", "3") + .build()} +connected + +# We receive an abort frame as the content is invalid +write abort +read abort diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7540/validation/invalid/server.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7540/validation/invalid/server.rpt new file mode 100644 index 0000000000..bdb39ddab1 --- /dev/null +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7540/validation/invalid/server.rpt @@ -0,0 +1,39 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +accept "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +# Request 4 - invalid content + +# We receive a begin frame as the headers are valid +read zilla:begin.ext ${http:matchBeginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123?page=1234567890123") + .header(":authority", "localhost:8080") + .header("code", "1234567890123") + .header("content-type", "text/plain") + .header("content-length", "3") + .build()} +connected + +# We receive an abort frame as the content is invalid +read aborted +write aborted diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7540/validation/valid/client.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7540/validation/valid/client.rpt new file mode 100644 index 0000000000..28ca1409d8 --- /dev/null +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7540/validation/valid/client.rpt @@ -0,0 +1,159 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +# Request 1 - valid path params +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123") + .header(":authority", "localhost:8080") + .build()} +connected + +write close + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-length", "9") + .build()} + +read "response1" +read closed +read notify RESPONSE_ONE_RECEIVED + + +# Request 2 - valid path params, query param +connect await RESPONSE_ONE_RECEIVED + "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123?page=1234567890123") + .header(":authority", "localhost:8080") + .build()} +connected + +write close + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-length", "9") + .build()} + +read "response2" +read closed +read notify RESPONSE_TWO_RECEIVED + + +# Request 3 - valid path params, url encoded query param +connect await RESPONSE_TWO_RECEIVED + "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123?%70%61%67%65=1234567890123") + .header(":authority", "localhost:8080") + .header("code", "1234567890123") + .build()} +connected + +write close + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-length", "9") + .build()} + +read "response3" +read closed +read notify RESPONSE_THREE_RECEIVED + + +# Request 4 - valid path params, query param, header field +connect await RESPONSE_THREE_RECEIVED + "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123?page=1234567890123") + .header(":authority", "localhost:8080") + .header("code", "1234567890123") + .build()} +connected + +write close + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-length", "9") + .build()} + +read "response4" +read closed + +read notify RESPONSE_FOUR_RECEIVED + + +# Request 5 - valid path params, query param, header field, valid content +connect await RESPONSE_FOUR_RECEIVED + "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123?page=1234567890123") + .header(":authority", "localhost:8080") + .header("code", "1234567890123") + .header("content-type", "text/plain;charset=UTF-8") + .header("content-length", "13") + .build()} +connected + +write "1234567890123" +write close + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-length", "9") + .build()} + +read "response5" +read closed diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7540/validation/valid/server.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7540/validation/valid/server.rpt new file mode 100644 index 0000000000..9ea96bbaf3 --- /dev/null +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/application/rfc7540/validation/valid/server.rpt @@ -0,0 +1,139 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +accept "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +# Request 1 - valid path params +accepted +read zilla:begin.ext ${http:matchBeginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123") + .header(":authority", "localhost:8080") + .build()} +connected +read closed + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-length", "9") + .build()} +write flush + +write "response1" +write close + + +# Request 2 - valid path params, query param +accepted +read zilla:begin.ext ${http:matchBeginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123?page=1234567890123") + .header(":authority", "localhost:8080") + .build()} +connected +read closed + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-length", "9") + .build()} +write flush + +write "response2" +write close + + +# Request 3 - valid path params, url encoded query param +accepted +read zilla:begin.ext ${http:matchBeginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123?%70%61%67%65=1234567890123") + .header(":authority", "localhost:8080") + .build()} +connected +read closed + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-length", "9") + .build()} +write flush + +write "response3" +write close + + +# Request 4 - valid path params, query param, header field +accepted +read zilla:begin.ext ${http:matchBeginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123?page=1234567890123") + .header(":authority", "localhost:8080") + .header("code", "1234567890123") + .build()} +connected +read closed + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-length", "9") + .build()} +write flush + +write "response4" +write close + + +# Request 5 - valid path params, query param, header field, content +accepted +read zilla:begin.ext ${http:matchBeginEx() + .typeId(zilla:id("http")) + .header(":scheme", "http") + .header(":method", "POST") + .header(":path", "/valid/1234567890123/1234567890123?page=1234567890123") + .header(":authority", "localhost:8080") + .header("code", "1234567890123") + .header("content-type", "text/plain;charset=UTF-8") + .header("content-length", "13") + .build()} +connected + +read "1234567890123" +read closed + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-length", "9") + .build()} +write flush + +write "response5" +write close diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/validation/invalid/client.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/validation/invalid/client.rpt new file mode 100644 index 0000000000..dfa1b825e6 --- /dev/null +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/validation/invalid/client.rpt @@ -0,0 +1,80 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/net0" + option zilla:window 8192 + option zilla:transmission "duplex" +connected + +# Request 1 - invalid path param +write "POST /valid/1234567890123/123 HTTP/1.1" "\r\n" +write "Host: localhost:8080" "\r\n" +write "\r\n" + +read "HTTP/1.1 400 Bad Request\r\n" +read "Connection: close\r\n" +read "\r\n" +read closed + +connect "zilla://streams/net0" + option zilla:window 8192 + option zilla:transmission "duplex" +connected + +# Request 2 - invalid query param +write "POST /valid/1234567890123/1234567890123?page=123 HTTP/1.1" "\r\n" +write "Host: localhost:8080" "\r\n" +write "\r\n" + +read "HTTP/1.1 400 Bad Request\r\n" +read "Connection: close\r\n" +read "\r\n" +read closed + +connect "zilla://streams/net0" + option zilla:window 8192 + option zilla:transmission "duplex" +connected + +# Request 3 - invalid header field +write "POST /valid/1234567890123/1234567890123?page=1234567890123 HTTP/1.1" "\r\n" +write "Host: localhost:8080" "\r\n" +write "Code: 123" "\r\n" +write "\r\n" + +read "HTTP/1.1 400 Bad Request\r\n" +read "Connection: close\r\n" +read "\r\n" +read closed + +connect "zilla://streams/net0" + option zilla:window 8192 + option zilla:transmission "duplex" +connected + +# Request 4 - invalid content +write "POST /valid/1234567890123/1234567890123?page=1234567890123 HTTP/1.1" "\r\n" +write "Host: localhost:8080" "\r\n" +write "Code: 1234567890123" "\r\n" +write "Content-Length: 3" "\r\n" +write "Content-Type: text/plain" "\r\n" +write "\r\n" +write "123" + +read "HTTP/1.1 400 Bad Request\r\n" +read "Connection: close\r\n" +read "\r\n" +read closed diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/validation/invalid/server.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/validation/invalid/server.rpt new file mode 100644 index 0000000000..6cffc94f85 --- /dev/null +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/validation/invalid/server.rpt @@ -0,0 +1,77 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property serverInitialWindow 8192 + +accept "zilla://streams/net0" + option zilla:window ${serverInitialWindow} + option zilla:transmission "duplex" +accepted +connected + +# Request 1 - invalid path param +read "POST /valid/1234567890123/123 HTTP/1.1" "\r\n" +read "Host: localhost:8080" "\r\n" +read "\r\n" + +write "HTTP/1.1 400 Bad Request\r\n" +write "Connection: close\r\n" +write "\r\n" +write close + +accepted +connected + +# Request 2 - invalid query param +read "POST /valid/1234567890123/1234567890123?page=123 HTTP/1.1" "\r\n" +read "Host: localhost:8080" "\r\n" +read "\r\n" + +write "HTTP/1.1 400 Bad Request\r\n" +write "Connection: close\r\n" +write "\r\n" +write close + +accepted +connected + +# Request 3 - invalid header field +read "POST /valid/1234567890123/1234567890123?page=1234567890123 HTTP/1.1" "\r\n" +read "Host: localhost:8080" "\r\n" +read "Code: 123" "\r\n" +read "\r\n" + +write "HTTP/1.1 400 Bad Request\r\n" +write "Connection: close\r\n" +write "\r\n" +write close + +accepted +connected + +# Request 4 - invalid content +read "POST /valid/1234567890123/1234567890123?page=1234567890123 HTTP/1.1" "\r\n" +read "Host: localhost:8080" "\r\n" +read "Code: 1234567890123" "\r\n" +read "Content-Length: 3" "\r\n" +read "Content-Type: text/plain" "\r\n" +read "\r\n" +read "123" + +write "HTTP/1.1 400 Bad Request\r\n" +write "Connection: close\r\n" +write "\r\n" +write close diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/validation/valid/client.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/validation/valid/client.rpt new file mode 100644 index 0000000000..e80034ddd7 --- /dev/null +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/validation/valid/client.rpt @@ -0,0 +1,79 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/net0" + option zilla:window 8192 + option zilla:transmission "duplex" +connected + +# Request 1 - valid path params +write "POST /valid/1234567890123/1234567890123 HTTP/1.1" "\r\n" +write "Host: localhost:8080" "\r\n" +write "\r\n" + +read "HTTP/1.1 200 OK\r\n" +read "Content-Length: 9" "\r\n" +read "\r\n" +read "response1" + + +# Request 2 - valid path params, query param +write "POST /valid/1234567890123/1234567890123?page=1234567890123 HTTP/1.1" "\r\n" +write "Host: localhost:8080" "\r\n" +write "\r\n" + +read "HTTP/1.1 200 OK\r\n" +read "Content-Length: 9" "\r\n" +read "\r\n" +read "response2" + + +# Request 3 - valid path params, url encoded query param +write "POST /valid/1234567890123/1234567890123?%70%61%67%65=1234567890123 HTTP/1.1" "\r\n" +write "Host: localhost:8080" "\r\n" +write "\r\n" + +read "HTTP/1.1 200 OK\r\n" +read "Content-Length: 9" "\r\n" +read "\r\n" +read "response3" + + +# Request 4 - valid path params, query param, header field +write "POST /valid/1234567890123/1234567890123?page=1234567890123 HTTP/1.1" "\r\n" +write "Host: localhost:8080" "\r\n" +write "Code: 1234567890123" "\r\n" +write "\r\n" + +read "HTTP/1.1 200 OK\r\n" +read "Content-Length: 9" "\r\n" +read "\r\n" +read "response4" + + +# Request 5 - valid path params, query param, header field, valid content +write "POST /valid/1234567890123/1234567890123?page=1234567890123 HTTP/1.1" "\r\n" +write "Host: localhost:8080" "\r\n" +write "Code: 1234567890123" "\r\n" +write "Content-Length: 13" "\r\n" +write "Content-Type: text/plain" "\r\n" +write "\r\n" +write "1234567890123" + +read "HTTP/1.1 200 OK\r\n" +read "Content-Length: 9" "\r\n" +read "\r\n" +read "response5" diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/validation/valid/server.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/validation/valid/server.rpt new file mode 100644 index 0000000000..f81ba768db --- /dev/null +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/validation/valid/server.rpt @@ -0,0 +1,82 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property serverInitialWindow 8192 + +accept "zilla://streams/net0" + option zilla:window ${serverInitialWindow} + option zilla:transmission "duplex" +accepted +connected + +# Request 1 - valid path params +read "POST /valid/1234567890123/1234567890123 HTTP/1.1" "\r\n" +read "Host: localhost:8080" "\r\n" +read "\r\n" + +write "HTTP/1.1 200 OK\r\n" +write "Content-Length: 9" "\r\n" +write "\r\n" +write "response1" + + +# Request 2 - valid path params, query param +read "POST /valid/1234567890123/1234567890123?page=1234567890123 HTTP/1.1" "\r\n" +read "Host: localhost:8080" "\r\n" +read "\r\n" + +write "HTTP/1.1 200 OK\r\n" +write "Content-Length: 9" "\r\n" +write "\r\n" +write "response2" + + +# Request 3 - valid path params, url encoded query param +read "POST /valid/1234567890123/1234567890123?%70%61%67%65=1234567890123 HTTP/1.1" "\r\n" +read "Host: localhost:8080" "\r\n" +read "\r\n" + +write "HTTP/1.1 200 OK\r\n" +write "Content-Length: 9" "\r\n" +write "\r\n" +write "response3" + + +# Request 4 - valid path params, query param, header field +read "POST /valid/1234567890123/1234567890123?page=1234567890123 HTTP/1.1" "\r\n" +read "Host: localhost:8080" "\r\n" +read "Code: 1234567890123" "\r\n" +read "\r\n" + +write "HTTP/1.1 200 OK\r\n" +write "Content-Length: 9" "\r\n" +write "\r\n" +write "response4" + + +# Request 5 - valid path params, query param, header field, valid content +read "POST /valid/1234567890123/1234567890123?page=1234567890123 HTTP/1.1" "\r\n" +read "Host: localhost:8080" "\r\n" +read "Code: 1234567890123" "\r\n" +read "Content-Length: 13" "\r\n" +read "Content-Type: text/plain" "\r\n" +read "\r\n" +read "1234567890123" + +write "HTTP/1.1 200 OK\r\n" +write "Content-Length: 9" "\r\n" +write "\r\n" +write "response5" diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7540/validation/invalid/client.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7540/validation/invalid/client.rpt new file mode 100644 index 0000000000..036747bc4f --- /dev/null +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7540/validation/invalid/client.rpt @@ -0,0 +1,142 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/net0" + option zilla:window 8192 + option zilla:transmission "duplex" +connected + +# client connection preface +write "PRI * HTTP/2.0\r\n" + "\r\n" + "SM\r\n" + "\r\n" +write flush + +# server connection preface - SETTINGS frame +read [0x00 0x00 0x12] # length = 18 + [0x04] # HTTP2 SETTINGS frame + [0x00] # flags = 0x00 + [0x00 0x00 0x00 0x00] # stream_id = 0 + [0x00 0x03 0x00 0x00 0x00 0x64] # SETTINGS_MAX_CONCURRENT_STREAMS(0x03) = 100 + [0x00 0x04 0x00 0x00 0x00 0x00] # SETTINGS_INITIAL_WINDOW_SIZE(0x04) = 0 + [0x00 0x06 0x00 0x00 0x20 0x00] # SETTINGS_MAX_HEADER_LIST_SIZE(0x06) = 8192 + +write [0x00 0x00 0x0c] # length = 12 + [0x04] # HTTP2 SETTINGS frame + [0x00] # flags = 0x00 + [0x00 0x00 0x00 0x00] # stream_id = 0 + [0x00 0x03 0x00 0x00 0x00 0x64] # SETTINGS_MAX_CONCURRENT_STREAMS(0x03) = 100 + [0x00 0x04 0x00 0x00 0xff 0xff] # SETTINGS_INITIAL_WINDOW_SIZE(0x04) = 65535 +write flush + + +# Request 1 - invalid path param +write [0x00 0x00 0x2c] # length = 44 + [0x01] # HEADERS frame + [0x05] # END_HEADERS | END_STREAM + [0x00 0x00 0x00 0x01] # stream_id = 1 + [0x83] # :method: POST + [0x86] # :scheme: http + [0x04] [0x18] # :path: /valid/1234567890123/123 + "/valid/1234567890123/123" + [0x01] [0x0e] "localhost:8080" # :authority: localhost:8080 +write flush + +read [0x00 0x00 0x00] # length = 0 + [0x04] # HTTP2 SETTINGS frame + [0x01] # ACK + [0x00 0x00 0x00 0x00] # stream_id = 0 + +read [0x00 0x00 0x05] # length = 5 + [0x01] # HTTP2 HEADERS frame + [0x05] # END_HEADERS | END_STREAM + [0x00 0x00 0x00 0x01] # stream_id = 1 + [0x8c] # :status: 400 + [0x0f 0x0d] [0x01] "0" # content-length + + +# Request 2 - invalid query param +write [0x00 0x00 0x3f] # length = 63 + [0x01] # HEADERS frame + [0x05] # END_HEADERS | END_STREAM + [0x00 0x00 0x00 0x03] # stream_id = 3 + [0x83] # :method: POST + [0x86] # :scheme: http + [0x04] [0x2B] # :path: /valid/1234567890123/1234567890123?page=123 + "/valid/1234567890123/1234567890123?page=123" + [0x01] [0x0E] "localhost:8080" # :authority: localhost:8080 +write flush + +read [0x00 0x00 0x05] # length = 5 + [0x01] # HTTP2 HEADERS frame + [0x05] # END_HEADERS | END_STREAM + [0x00 0x00 0x00 0x03] # stream_id = 3 + [0x8c] # :status: 400 + [0x0f 0x0d] [0x01] "0" # content-length + + +# Request 3 - invalid header field +write [0x00 0x00 0x53] # length = 83 + [0x01] # HEADERS frame + [0x05] # END_HEADERS | END_STREAM + [0x00 0x00 0x00 0x05] # stream_id = 5 + [0x83] # :method: POST + [0x86] # :scheme: http + [0x04] [0x35] # :path: /valid/1234567890123/1234567890123?page=1234567890123 + "/valid/1234567890123/1234567890123?page=1234567890123" + [0x01] [0x0e] "localhost:8080" # :authority: localhost:8080 + [0x40] [0x04] "code" + [0x03] "123" # code: 123 +write flush + +read [0x00 0x00 0x05] # length = 5 + [0x01] # HTTP2 HEADERS frame + [0x05] # END_HEADERS | END_STREAM + [0x00 0x00 0x00 0x05] # stream_id = 5 + [0x8c] # :status: 400 + [0x0f 0x0d] [0x01] "0" # content-length + + +# Request 4 - invalid content +write [0x00 0x00 0x6e] # length = 110 + [0x01] # HEADERS frame + [0x04] # END_HEADERS + [0x00 0x00 0x00 0x07] # stream_id = 7 + [0x83] # :method: POST + [0x86] # :scheme: http + [0x04] [0x35] # :path: /valid/1234567890123/1234567890123?page=1234567890123 + "/valid/1234567890123/1234567890123?page=1234567890123" + [0x01] [0x0e] "localhost:8080" # :authority: localhost:8080 + [0x40] [0x04] "code" + [0x0d] "1234567890123" # code: 1234567890123 + [0x0f 0x10] [0x0a] "text/plain" # content-type + [0x0f 0x0d] [0x01] "3" # content-length +write flush + +write [0x00 0x00 0x03] # length = 3 + [0x00] # HTTP2 DATA frame + [0x01] # END_STREAM + [0x00 0x00 0x00 0x07] # stream_id = 7 + "123" +write flush + +read [0x00 0x00 0x05] # length = 5 + [0x01] # HTTP2 HEADERS frame + [0x05] # END_HEADERS | END_STREAM + [0x00 0x00 0x00 0x07] # stream_id = 7 + [0x8c] # :status: 400 + [0x0f 0x0d] [0x01] "0" # content-length diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7540/validation/invalid/server.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7540/validation/invalid/server.rpt new file mode 100644 index 0000000000..e2ffdb4764 --- /dev/null +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7540/validation/invalid/server.rpt @@ -0,0 +1,138 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property serverInitialWindow 8192 + +accept "zilla://streams/net0" + option zilla:window ${serverInitialWindow} + option zilla:transmission "duplex" +accepted +connected + +# client connection preface +read "PRI * HTTP/2.0\r\n" + "\r\n" + "SM\r\n" + "\r\n" + +# server connection preface - SETTINGS frame +write [0x00 0x00 0x12] # length = 18 + [0x04] # HTTP2 SETTINGS frame + [0x00] # flags = 0x00 + [0x00 0x00 0x00 0x00] # stream_id = 0 + [0x00 0x03 0x00 0x00 0x00 0x64] # SETTINGS_MAX_CONCURRENT_STREAMS(0x03) = 100 + [0x00 0x04 0x00 0x00 0x00 0x00] # SETTINGS_INITIAL_WINDOW_SIZE(0x04) = 0 + [0x00 0x06 0x00 0x00 0x20 0x00] # SETTINGS_MAX_HEADER_LIST_SIZE(0x06) = 8192 + +read [0x00 0x00 0x0c] # length = 12 + [0x04] # HTTP2 SETTINGS frame + [0x00] # flags = 0x00 + [0x00 0x00 0x00 0x00] # stream_id = 0 + [0x00 0x03 0x00 0x00 0x00 0x64] # SETTINGS_MAX_CONCURRENT_STREAMS(0x03) = 100 + [0x00 0x04 0x00 0x00 0xff 0xff] # SETTINGS_INITIAL_WINDOW_SIZE(0x04) = 65535 + + +# Request 1 - invalid path param +read [0x00 0x00 0x2c] # length = 44 + [0x01] # HEADERS frame + [0x05] # END_HEADERS | END_STREAM + [0x00 0x00 0x00 0x01] # stream_id = 1 + [0x83] # :method: POST + [0x86] # :scheme: http + [0x04] [0x18] # :path: /valid/1234567890123/123 + "/valid/1234567890123/123" + [0x01] [0x0e] "localhost:8080" # :authority: localhost:8080 + +write [0x00 0x00 0x00] # length = 0 + [0x04] # HTTP2 SETTINGS frame + [0x01] # ACK + [0x00 0x00 0x00 0x00] # stream_id = 0 + +write [0x00 0x00 0x05] # length = 5 + [0x01] # HTTP2 HEADERS frame + [0x05] # END_HEADERS | END_STREAM + [0x00 0x00 0x00 0x01] # stream_id = 1 + [0x8c] # :status: 400 + [0x0f 0x0d] [0x01] "0" # content-length + + +# Request 2 - invalid query param +read [0x00 0x00 0x3f] # length = 63 + [0x01] # HEADERS frame + [0x05] # END_HEADERS | END_STREAM + [0x00 0x00 0x00 0x03] # stream_id = 3 + [0x83] # :method: POST + [0x86] # :scheme: http + [0x04] [0x2b] # :path: /valid/1234567890123/1234567890123?page=123 + "/valid/1234567890123/1234567890123?page=123" + [0x01] [0x0e] "localhost:8080" # :authority: localhost:8080 + +write [0x00 0x00 0x05] # length = 5 + [0x01] # HTTP2 HEADERS frame + [0x05] # END_HEADERS | END_STREAM + [0x00 0x00 0x00 0x03] # stream_id = 3 + [0x8c] # :status: 400 + [0x0f 0x0d] [0x01] "0" # content-length + + +# Request 3 - invalid header field +read [0x00 0x00 0x53] # length = 83 + [0x01] # HEADERS frame + [0x05] # END_HEADERS | END_STREAM + [0x00 0x00 0x00 0x05] # stream_id = 5 + [0x83] # :method: POST + [0x86] # :scheme: http + [0x04] [0x35] # :path: /valid/1234567890123/1234567890123?page=1234567890123 + "/valid/1234567890123/1234567890123?page=1234567890123" + [0x01] [0x0e] "localhost:8080" # :authority: localhost:8080 + [0x40] [0x04] "code" + [0x03] "123" # code: 123 + +write [0x00 0x00 0x05] # length = 5 + [0x01] # HTTP2 HEADERS frame + [0x05] # END_HEADERS | END_STREAM + [0x00 0x00 0x00 0x05] # stream_id = 5 + [0x8c] # :status: 400 + [0x0f 0x0d] [0x01] "0" # content-length + + +# Request 4 - invalid content +read [0x00 0x00 0x6e] # length = 110 + [0x01] # HEADERS frame + [0x04] # END_HEADERS + [0x00 0x00 0x00 0x07] # stream_id = 7 + [0x83] # :method: POST + [0x86] # :scheme: http + [0x04] [0x35] # :path: /valid/1234567890123/1234567890123?page=1234567890123 + "/valid/1234567890123/1234567890123?page=1234567890123" + [0x01] [0x0e] "localhost:8080" # :authority: localhost:8080 + [0x40] [0x04] "code" + [0x0d] "1234567890123" # code: 1234567890123 + [0x0f 0x10] [0x0a] "text/plain" # content-type + [0x0f 0x0d] [0x01] "3" # content-length + +read [0x00 0x00 0x03] # length = 3 + [0x00] # HTTP2 DATA frame + [0x01] # END_STREAM + [0x00 0x00 0x00 0x07] # stream_id = 7 + "123" + +write [0x00 0x00 0x05] # length = 5 + [0x01] # HTTP2 HEADERS frame + [0x05] # END_HEADERS | END_STREAM + [0x00 0x00 0x00 0x07] # stream_id = 7 + [0x8c] # :status: 400 + [0x0f 0x0d] [0x01] "0" # content-length diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7540/validation/valid/client.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7540/validation/valid/client.rpt new file mode 100644 index 0000000000..a06371c293 --- /dev/null +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7540/validation/valid/client.rpt @@ -0,0 +1,211 @@ +## +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/net0" + option zilla:window 8192 + option zilla:transmission "duplex" +connected + +# client connection preface +write "PRI * HTTP/2.0\r\n" + "\r\n" + "SM\r\n" + "\r\n" +write flush + +# server connection preface - SETTINGS frame +read [0x00 0x00 0x12] # length = 18 + [0x04] # HTTP2 SETTINGS frame + [0x00] # flags = 0x00 + [0x00 0x00 0x00 0x00] # stream_id = 0 + [0x00 0x03 0x00 0x00 0x00 0x64] # SETTINGS_MAX_CONCURRENT_STREAMS(0x03) = 100 + [0x00 0x04 0x00 0x00 0x00 0x00] # SETTINGS_INITIAL_WINDOW_SIZE(0x04) = 0 + [0x00 0x06 0x00 0x00 0x20 0x00] # SETTINGS_MAX_HEADER_LIST_SIZE(0x06) = 8192 + +write [0x00 0x00 0x0c] # length = 12 + [0x04] # HTTP2 SETTINGS frame + [0x00] # flags = 0x00 + [0x00 0x00 0x00 0x00] # stream_id = 0 + [0x00 0x03 0x00 0x00 0x00 0x64] # SETTINGS_MAX_CONCURRENT_STREAMS(0x03) = 100 + [0x00 0x04 0x00 0x00 0xff 0xff] # SETTINGS_INITIAL_WINDOW_SIZE(0x04) = 65535 +write flush + + +# Request 1 - valid path params +write [0x00 0x00 0x36] # length = 54 + [0x01] # HEADERS frame + [0x05] # END_HEADERS | END_STREAM + [0x00 0x00 0x00 0x01] # stream_id = 1 + [0x83] # :method: POST + [0x86] # :scheme: http + [0x04] [0x22] # :path: /valid/1234567890123/1234567890123 + "/valid/1234567890123/1234567890123" + [0x01] [0x0e] "localhost:8080" # :authority: localhost:8080 +write flush + +write [0x00 0x00 0x00] # length = 0 + [0x04] # HTTP2 SETTINGS frame + [0x01] # ACK + [0x00 0x00 0x00 0x00] # stream_id = 0 +write flush + +read [0x00 0x00 0x00] # length = 0 + [0x04] # HTTP2 SETTINGS frame + [0x01] # ACK + [0x00 0x00 0x00 0x00] # stream_id = 0 + +read [0x00 0x00 0x05] # length = 5 + [0x01] # HTTP2 HEADERS frame + [0x04] # END_HEADERS + [0x00 0x00 0x00 0x01] # stream_id = 1 + [0x88] # :status: 200 + [0x0f 0x0d] [0x01] "9" # content-length + +read [0x00 0x00 0x09] # length = 9 + [0x00] # HTTP2 DATA frame + [0x01] # END_STREAM + [0x00 0x00 0x00 0x01] # stream_id = 1 + "response1" + + +# Request 2 - valid path params, query param +write [0x00 0x00 0x49] # length = 73 + [0x01] # HEADERS frame + [0x05] # END_HEADERS | END_STREAM + [0x00 0x00 0x00 0x03] # stream_id = 3 + [0x83] # :method: POST + [0x86] # :scheme: http + [0x04] [0x35] # :path: /valid/1234567890123/1234567890123?page=1234567890123 + "/valid/1234567890123/1234567890123?page=1234567890123" + [0x01] [0x0e] "localhost:8080" # :authority: localhost:8080 +write flush + +read [0x00 0x00 0x05] # length = 5 + [0x01] # HTTP2 HEADERS frame + [0x04] # END_HEADERS + [0x00 0x00 0x00 0x03] # stream_id = 3 + [0x88] # :status: 200 + [0x0f 0x0d] [0x01] "9" # content-length + +read [0x00 0x00 0x09] # length = 9 + [0x00] # HTTP2 DATA frame + [0x01] # END_STREAM + [0x00 0x00 0x00 0x03] # stream_id = 3 + "response2" + + +# Request 3 - valid path params, url encoded query param +write [0x00 0x00 0x51] # length = 81 + [0x01] # HEADERS frame + [0x05] # END_HEADERS | END_STREAM + [0x00 0x00 0x00 0x05] # stream_id = 5 + [0x83] # :method: POST + [0x86] # :scheme: http + [0x04] [0x3d] # :path: /valid/1234567890123/1234567890123?%70%61%67%65=1234567890123 + "/valid/1234567890123/1234567890123?%70%61%67%65=1234567890123" + [0x01] [0x0e] "localhost:8080" # :authority: localhost:8080 +write flush + +read [0x00 0x00 0x05] # length = 5 + [0x01] # HTTP2 HEADERS frame + [0x04] # END_HEADERS + [0x00 0x00 0x00 0x05] # stream_id = 5 + [0x88] # :status: 200 + [0x0f 0x0d] [0x01] "9" # content-length + +read [0x00 0x00 0x09] # length = 9 + [0x00] # HTTP2 DATA frame + [0x01] # END_STREAM + [0x00 0x00 0x00 0x05] # stream_id = 5 + "response3" + + +# Request 4 - valid path params, query param, header field +write [0x00 0x00 0x5d] # length = 93 + [0x01] # HEADERS frame + [0x05] # END_HEADERS | END_STREAM + [0x00 0x00 0x00 0x07] # stream_id = 7 + [0x83] # :method: POST + [0x86] # :scheme: http + [0x04] [0x35] # :path: /valid/1234567890123/1234567890123?page=1234567890123 + "/valid/1234567890123/1234567890123?page=1234567890123" + [0x01] [0x0e] "localhost:8080" # :authority: localhost:8080 + [0x40] [0x04] "code" + [0x0d] "1234567890123" # code: 1234567890123 +write flush + +read [0x00 0x00 0x05] # length = 5 + [0x01] # HTTP2 HEADERS frame + [0x04] # END_HEADERS + [0x00 0x00 0x00 0x07] # stream_id = 7 + [0x88] # :status: 200 + [0x0f 0x0d] [0x01] "9" # content-length + +read [0x00 0x00 0x09] # length = 9 + [0x00] # HTTP2 DATA frame + [0x01] # END_STREAM + [0x00 0x00 0x00 0x07] # stream_id = 7 + "response4" + + +# Request 5 - valid path params, query param, header field, content +write [0x00 0x00 0x7d] # length = 125 + [0x01] # HEADERS frame + [0x04] # END_HEADERS + [0x00 0x00 0x00 0x09] # stream_id = 9 + [0x83] # :method: POST + [0x86] # :scheme: http + [0x04] [0x35] # :path: /valid/1234567890123/1234567890123?page=1234567890123 + "/valid/1234567890123/1234567890123?page=1234567890123" + [0x01] [0x0e] "localhost:8080" # :authority: localhost:8080 + [0x40] [0x04] "code" + [0x0d] "1234567890123" # code: 1234567890123 + [0x0f] [0x10] # content-type + [0x18] "text/plain;charset=UTF-8" + [0x0f 0x0d] [0x02] "13" # content-length +write flush + +read [0x00 0x00 0x04] # length + [0x08] # WINDOW_UPDATE frame + [0x00] # no flags + [0x00 0x00 0x00 0x00] # stream_id = 0 + [0x00 0x00 0x20 0x00] # window size increment = 8192 + +read [0x00 0x00 0x04] # length + [0x08] # WINDOW_UPDATE frame + [0x00] # no flags + [0x00 0x00 0x00 0x09] # stream_id = 9 + [0x00 0x00 0x20 0x00] # window size increment = 8192 + +write [0x00 0x00 0x0d] # length = 13 + [0x00] # HTTP2 DATA frame + [0x01] # END_STREAM + [0x00 0x00 0x00 0x09] # stream_id = 9 + "1234567890123" +write flush + +read [0x00 0x00 0x05] # length = 5 + [0x01] # HTTP2 HEADERS frame + [0x04] # END_HEADERS + [0x00 0x00 0x00 0x09] # stream_id = 9 + [0x88] # :status: 200 + [0x0f 0x0d] [0x01] "9" # content-length + +read [0x00 0x00 0x09] # length = 9 + [0x00] # HTTP2 DATA frame + [0x01] # END_STREAM + [0x00 0x00 0x00 0x09] # stream_id = 9 + "response5" diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7540/validation/valid/server.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7540/validation/valid/server.rpt new file mode 100644 index 0000000000..93180d96ed --- /dev/null +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7540/validation/valid/server.rpt @@ -0,0 +1,205 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property serverInitialWindow 8192 + +accept "zilla://streams/net0" + option zilla:window ${serverInitialWindow} + option zilla:transmission "duplex" +accepted +connected + +# client connection preface +read "PRI * HTTP/2.0\r\n" + "\r\n" + "SM\r\n" + "\r\n" + +# server connection preface - SETTINGS frame +write [0x00 0x00 0x12] # length = 18 + [0x04] # HTTP2 SETTINGS frame + [0x00] # flags = 0x00 + [0x00 0x00 0x00 0x00] # stream_id = 0 + [0x00 0x03 0x00 0x00 0x00 0x64] # SETTINGS_MAX_CONCURRENT_STREAMS(0x03) = 100 + [0x00 0x04 0x00 0x00 0x00 0x00] # SETTINGS_INITIAL_WINDOW_SIZE(0x04) = 0 + [0x00 0x06 0x00 0x00 0x20 0x00] # SETTINGS_MAX_HEADER_LIST_SIZE(0x06) = 8192 + +read [0x00 0x00 0x0c] # length = 12 + [0x04] # HTTP2 SETTINGS frame + [0x00] # flags = 0x00 + [0x00 0x00 0x00 0x00] # stream_id = 0 + [0x00 0x03 0x00 0x00 0x00 0x64] # SETTINGS_MAX_CONCURRENT_STREAMS(0x03) = 100 + [0x00 0x04 0x00 0x00 0xff 0xff] # SETTINGS_INITIAL_WINDOW_SIZE(0x04) = 65535 + + +# Request 1 - valid path params +read [0x00 0x00 0x36] # length = 54 + [0x01] # HEADERS frame + [0x05] # END_HEADERS | END_STREAM + [0x00 0x00 0x00 0x01] # stream_id = 1 + [0x83] # :method: POST + [0x86] # :scheme: http + [0x04] [0x22] # :path: /valid/1234567890123/1234567890123 + "/valid/1234567890123/1234567890123" + [0x01] [0x0E] "localhost:8080" # :authority: localhost:8080 + +read [0x00 0x00 0x00] # length = 0 + [0x04] # HTTP2 SETTINGS frame + [0x01] # ACK + [0x00 0x00 0x00 0x00] # stream_id = 0 + +write [0x00 0x00 0x00] # length = 0 + [0x04] # HTTP2 SETTINGS frame + [0x01] # ACK + [0x00 0x00 0x00 0x00] # stream_id = 0 + +write [0x00 0x00 0x05] # length = 5 + [0x01] # HTTP2 HEADERS frame + [0x04] # END_HEADERS + [0x00 0x00 0x00 0x01] # stream_id = 1 + [0x88] # :status: 200 + [0x0f 0x0d] [0x01] "9" # content-length + +write [0x00 0x00 0x09] # length = 9 + [0x00] # HTTP2 DATA frame + [0x01] # END_STREAM + [0x00 0x00 0x00 0x01] # stream_id = 1 + "response1" + + +# Request 2 - valid path params, query param +read [0x00 0x00 0x49] # length = 73 + [0x01] # HEADERS frame + [0x05] # END_HEADERS | END_STREAM + [0x00 0x00 0x00 0x03] # stream_id = 3 + [0x83] # :method: POST + [0x86] # :scheme: http + [0x04] [0x35] # :path: /valid/1234567890123/1234567890123?page=1234567890123 + "/valid/1234567890123/1234567890123?page=1234567890123" + [0x01] [0x0e] "localhost:8080" # :authority: localhost:8080 + +write [0x00 0x00 0x05] # length = 5 + [0x01] # HTTP2 HEADERS frame + [0x04] # END_HEADERS + [0x00 0x00 0x00 0x03] # stream_id = 3 + [0x88] # :status: 200 + [0x0f 0x0d] [0x01] "9" # content-length + +write [0x00 0x00 0x09] # length = 9 + [0x00] # HTTP2 DATA frame + [0x01] # END_STREAM + [0x00 0x00 0x00 0x03] # stream_id = 3 + "response2" + + +# Request 3 - valid path params, url encoded query param +read [0x00 0x00 0x51] # length = 81 + [0x01] # HEADERS frame + [0x05] # END_HEADERS | END_STREAM + [0x00 0x00 0x00 0x05] # stream_id = 5 + [0x83] # :method: POST + [0x86] # :scheme: http + [0x04] [0x3d] # :path: /valid/1234567890123/1234567890123?%70%61%67%65=1234567890123 + "/valid/1234567890123/1234567890123?%70%61%67%65=1234567890123" + [0x01] [0x0e] "localhost:8080" # :authority: localhost:8080 + +write [0x00 0x00 0x05] # length = 5 + [0x01] # HTTP2 HEADERS frame + [0x04] # END_HEADERS + [0x00 0x00 0x00 0x05] # stream_id = 5 + [0x88] # :status: 200 + [0x0f 0x0d] [0x01] "9" # content-length + +write [0x00 0x00 0x09] # length = 9 + [0x00] # HTTP2 DATA frame + [0x01] # END_STREAM + [0x00 0x00 0x00 0x05] # stream_id = 5 + "response3" + + +# Request 4 - valid path params, query param, header field +read [0x00 0x00 0x5d] # length = 93 + [0x01] # HEADERS frame + [0x05] # END_HEADERS | END_STREAM + [0x00 0x00 0x00 0x07] # stream_id = 7 + [0x83] # :method: POST + [0x86] # :scheme: http + [0x04] [0x35] # :path: /valid/1234567890123/1234567890123?page=1234567890123 + "/valid/1234567890123/1234567890123?page=1234567890123" + [0x01] [0x0e] "localhost:8080" # :authority: localhost:8080 + [0x40] [0x04] "code" + [0x0d] "1234567890123" # code: 1234567890123 + +write [0x00 0x00 0x05] # length = 5 + [0x01] # HTTP2 HEADERS frame + [0x04] # END_HEADERS + [0x00 0x00 0x00 0x07] # stream_id = 7 + [0x88] # :status: 200 + [0x0f 0x0d] [0x01] "9" # content-length + +write [0x00 0x00 0x09] # length = 9 + [0x00] # HTTP2 DATA frame + [0x01] # END_STREAM + [0x00 0x00 0x00 0x07] # stream_id = 7 + "response4" + + +# Request 5 - valid path params, query param, header field, content +read [0x00 0x00 0x7d] # length = 125 + [0x01] # HEADERS frame + [0x04] # END_HEADERS + [0x00 0x00 0x00 0x09] # stream_id = 9 + [0x83] # :method: POST + [0x86] # :scheme: http + [0x04] [0x35] # :path: /valid/1234567890123/1234567890123?page=1234567890123 + "/valid/1234567890123/1234567890123?page=1234567890123" + [0x01] [0x0e] "localhost:8080" # :authority: localhost:8080 + [0x40] [0x04] "code" + [0x0d] "1234567890123" # code: 1234567890123 + [0x0f] [0x10] # content-type + [0x18] "text/plain;charset=UTF-8" + [0x0f 0x0d] [0x02] "13" # content-length + +write [0x00 0x00 0x04] # length + [0x08] # WINDOW_UPDATE frame + [0x00] # no flags + [0x00 0x00 0x00 0x00] # stream_id = 0 + [0x00 0x00 0x20 0x00] # window size increment = 8192 + +write [0x00 0x00 0x04] # length + [0x08] # WINDOW_UPDATE frame + [0x00] # no flags + [0x00 0x00 0x00 0x09] # stream_id = 9 + [0x00 0x00 0x20 0x00] # window size increment = 8192 + +read [0x00 0x00 0x0d] # length = 13 + [0x00] # HTTP2 DATA frame + [0x01] # END_STREAM + [0x00 0x00 0x00 0x09] # stream_id = 9 + "1234567890123" + +write [0x00 0x00 0x05] # length = 5 + [0x01] # HTTP2 HEADERS frame + [0x04] # END_HEADERS + [0x00 0x00 0x00 0x09] # stream_id = 9 + [0x88] # :status: 200 + [0x0f 0x0d] [0x01] "9" # content-length + +write [0x00 0x00 0x09] # length = 9 + [0x00] # HTTP2 DATA frame + [0x01] # END_STREAM + [0x00 0x00 0x00 0x09] # stream_id = 9 + "response5" diff --git a/specs/binding-http.spec/src/test/java/io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/ValidationIT.java b/specs/binding-http.spec/src/test/java/io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/ValidationIT.java new file mode 100644 index 0000000000..36887d6b3c --- /dev/null +++ b/specs/binding-http.spec/src/test/java/io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/ValidationIT.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.http.streams.application.rfc7230; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class ValidationIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("app", "io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/validation"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(5, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + @Test + @Specification({ + "${app}/invalid/client", + "${app}/invalid/server" }) + public void shouldRejectInvalidRequests() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${app}/valid/client", + "${app}/valid/server" }) + public void shouldProcessValidRequests() throws Exception + { + k3po.finish(); + } +} diff --git a/specs/binding-http.spec/src/test/java/io/aklivity/zilla/specs/binding/http/streams/application/rfc7540/ValidationIT.java b/specs/binding-http.spec/src/test/java/io/aklivity/zilla/specs/binding/http/streams/application/rfc7540/ValidationIT.java new file mode 100644 index 0000000000..55854f56bf --- /dev/null +++ b/specs/binding-http.spec/src/test/java/io/aklivity/zilla/specs/binding/http/streams/application/rfc7540/ValidationIT.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.http.streams.application.rfc7540; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class ValidationIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("app", "io/aklivity/zilla/specs/binding/http/streams/application/rfc7540/validation"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(5, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + @Test + @Specification({ + "${app}/invalid/client", + "${app}/invalid/server" }) + public void shouldRejectInvalidRequests() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${app}/valid/client", + "${app}/valid/server" }) + public void shouldProcessValidRequests() throws Exception + { + k3po.finish(); + } +} diff --git a/specs/binding-http.spec/src/test/java/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/ValidationIT.java b/specs/binding-http.spec/src/test/java/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/ValidationIT.java new file mode 100644 index 0000000000..e447c10397 --- /dev/null +++ b/specs/binding-http.spec/src/test/java/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/ValidationIT.java @@ -0,0 +1,58 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.http.streams.network.rfc7230; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class ValidationIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("net", "io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/validation"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + @Test + @Specification({ + "${net}/valid/client", + "${net}/valid/server" }) + public void shouldProcessValidRequests() throws Exception + { + k3po.start(); + k3po.finish(); + } + + @Test + @Specification({ + "${net}/invalid/client", + "${net}/invalid/server" }) + public void shouldRejectInvalidRequests() throws Exception + { + k3po.start(); + k3po.finish(); + } +} diff --git a/specs/binding-http.spec/src/test/java/io/aklivity/zilla/specs/binding/http/streams/network/rfc7540/ValidationIT.java b/specs/binding-http.spec/src/test/java/io/aklivity/zilla/specs/binding/http/streams/network/rfc7540/ValidationIT.java new file mode 100644 index 0000000000..bdb6cfb884 --- /dev/null +++ b/specs/binding-http.spec/src/test/java/io/aklivity/zilla/specs/binding/http/streams/network/rfc7540/ValidationIT.java @@ -0,0 +1,58 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.http.streams.network.rfc7540; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class ValidationIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("net", "io/aklivity/zilla/specs/binding/http/streams/network/rfc7540/validation"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + @Test + @Specification({ + "${net}/valid/client", + "${net}/valid/server" }) + public void shouldProcessValidRequests() throws Exception + { + k3po.start(); + k3po.finish(); + } + + @Test + @Specification({ + "${net}/invalid/client", + "${net}/invalid/server" }) + public void shouldRejectInvalidRequests() throws Exception + { + k3po.start(); + k3po.finish(); + } +} diff --git a/specs/binding-kafka-grpc.spec/pom.xml b/specs/binding-kafka-grpc.spec/pom.xml index 42fb9649f5..e5e2967f90 100644 --- a/specs/binding-kafka-grpc.spec/pom.xml +++ b/specs/binding-kafka-grpc.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/binding-kafka.spec/pom.xml b/specs/binding-kafka.spec/pom.xml index 31f04728b9..6de19afc2a 100644 --- a/specs/binding-kafka.spec/pom.xml +++ b/specs/binding-kafka.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.produce.flush.dynamic.hashed/client.rpt b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.produce.flush.dynamic.hashed/client.rpt index ffb8003d11..f4952fda14 100644 --- a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.produce.flush.dynamic.hashed/client.rpt +++ b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.produce.flush.dynamic.hashed/client.rpt @@ -112,8 +112,8 @@ write advise zilla:flush ${kafka:flushEx() .merged() .fetch() .partition(-1, -1) - .key("key9") .capabilities("PRODUCE_ONLY") + .key("key9") .build() .build()} diff --git a/specs/binding-mqtt-kafka.spec/pom.xml b/specs/binding-mqtt-kafka.spec/pom.xml index 7e2088bd1c..96e7a01b51 100644 --- a/specs/binding-mqtt-kafka.spec/pom.xml +++ b/specs/binding-mqtt-kafka.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/binding-mqtt-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/kafka/session.close.expire.session.state/client.rpt b/specs/binding-mqtt-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/kafka/session.close.expire.session.state/client.rpt index 4c092b32a4..f4f989e364 100644 --- a/specs/binding-mqtt-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/kafka/session.close.expire.session.state/client.rpt +++ b/specs/binding-mqtt-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/kafka/session.close.expire.session.state/client.rpt @@ -84,6 +84,23 @@ read ${mqtt:sessionSignal() read notify RECEIVED_WILL_DELIVER_AT_SIGNAL +read zilla:data.ext ${kafka:matchDataEx() + .typeId(zilla:id("kafka")) + .merged() + .deferred(0) + .partition(-1, -1) + .key("client-1#expiry-signal") + .header("type", "expiry-signal") + .build() + .build()} +read ${mqtt:sessionSignal() + .expiry() + .instanceId("zilla-1") + .clientId("client-2") + .delay(1000) + .expireAt(2000) + .build() + .build()} write zilla:data.ext ${kafka:dataEx() .typeId(zilla:id("kafka")) @@ -96,6 +113,17 @@ write zilla:data.ext ${kafka:dataEx() .build()} write flush +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .merged() + .deferred(0) + .partition(-1, -1) + .key("client-2") + .hashKey("client-2") + .build() + .build()} +write flush + connect "zilla://streams/kafka0" option zilla:window 8192 diff --git a/specs/binding-mqtt-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/kafka/session.close.expire.session.state/server.rpt b/specs/binding-mqtt-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/kafka/session.close.expire.session.state/server.rpt index 861a9a642b..6cf1a76540 100644 --- a/specs/binding-mqtt-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/kafka/session.close.expire.session.state/server.rpt +++ b/specs/binding-mqtt-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/kafka/session.close.expire.session.state/server.rpt @@ -92,6 +92,25 @@ write ${mqtt:sessionSignal() .build()} write flush +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .merged() + .deferred(0) + .partition(-1, -1) + .key("client-1#expiry-signal") + .header("type", "expiry-signal") + .build() + .build()} +write ${mqtt:sessionSignal() + .expiry() + .instanceId("zilla-1") + .clientId("client-2") + .delay(1000) + .expireAt(2000) + .build() + .build()} +write flush + # cleanup session state read zilla:data.ext ${kafka:matchDataEx() .typeId(zilla:id("kafka")) @@ -104,6 +123,17 @@ read zilla:data.ext ${kafka:matchDataEx() .build()} read zilla:data.null +# cleanup session state +read zilla:data.ext ${kafka:matchDataEx() + .typeId(zilla:id("kafka")) + .merged() + .deferred(0) + .partition(-1, -1) + .key("client-2") + .hashKey("client-2") + .build() + .build()} +read zilla:data.null accepted diff --git a/specs/binding-mqtt-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/kafka/session.expiry.after.signal.stream.restart/client.rpt b/specs/binding-mqtt-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/kafka/session.expiry.after.signal.stream.restart/client.rpt new file mode 100644 index 0000000000..c2c1e3be82 --- /dev/null +++ b/specs/binding-mqtt-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/kafka/session.expiry.after.signal.stream.restart/client.rpt @@ -0,0 +1,102 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +connect "zilla://streams/kafka0" + option zilla:window 8192 + option zilla:transmission "duplex" + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_AND_FETCH") + .topic("mqtt-sessions") + .groupId("mqtt-clients") + .filter() + .header("type", "will-signal") + .build() + .filter() + .header("type", "expiry-signal") + .build() + .build() + .build()} + +connected + +read zilla:data.ext ${kafka:matchDataEx() + .typeId(zilla:id("kafka")) + .merged() + .deferred(0) + .partition(-1, -1) + .key("client-1#expiry-signal") + .header("type", "expiry-signal") + .build() + .build()} +read [0x01 0x07 0x00 0x7a 0x69 0x6c 0x6c 0x61 0x2d 0x31 0x08 0x00 0x63 0x6c 0x69 0x65 0x6e 0x74 0x2d] + +read aborted +read notify RECEIVED_SIGNAL_STREAM_ABORT +write abort + + +connect await RECEIVED_SIGNAL_STREAM_ABORT + "zilla://streams/kafka0" + option zilla:window 8192 + option zilla:transmission "duplex" + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_AND_FETCH") + .topic("mqtt-sessions") + .groupId("mqtt-clients") + .filter() + .header("type", "will-signal") + .build() + .filter() + .header("type", "expiry-signal") + .build() + .build() + .build()} + +connected + +read zilla:data.ext ${kafka:matchDataEx() + .typeId(zilla:id("kafka")) + .merged() + .deferred(0) + .partition(-1, -1) + .key("client-1#expiry-signal") + .header("type", "expiry-signal") + .build() + .build()} +read ${mqtt:sessionSignal() + .expiry() + .instanceId("zilla-1") + .clientId("client-1") + .delay(1000) + .expireAt(expireAt) + .build() + .build()} + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .merged() + .deferred(0) + .partition(-1, -1) + .key("client-1") + .hashKey("client-1") + .build() + .build()} +write flush diff --git a/specs/binding-mqtt-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/kafka/session.expiry.after.signal.stream.restart/server.rpt b/specs/binding-mqtt-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/kafka/session.expiry.after.signal.stream.restart/server.rpt new file mode 100644 index 0000000000..baaf9befa5 --- /dev/null +++ b/specs/binding-mqtt-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/kafka/session.expiry.after.signal.stream.restart/server.rpt @@ -0,0 +1,107 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +property delayMillis 1000L +property expireAt ${mqtt:timestamp() + delayMillis} + +accept "zilla://streams/kafka0" + option zilla:window 8192 + option zilla:transmission "duplex" + + +accepted + +read zilla:begin.ext ${kafka:matchBeginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_AND_FETCH") + .topic("mqtt-sessions") + .groupId("mqtt-clients") + .filter() + .header("type", "will-signal") + .build() + .filter() + .header("type", "expiry-signal") + .build() + .build() + .build()} + +connected + +write option zilla:flags "init" +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .merged() + .deferred(0) + .partition(-1, -1) + .key("client-1#expiry-signal") + .header("type", "expiry-signal") + .build() + .build()} +write [0x01 0x07 0x00 0x7a 0x69 0x6c 0x6c 0x61 0x2d 0x31 0x08 0x00 0x63 0x6c 0x69 0x65 0x6e 0x74 0x2d] +write flush + +write abort +read aborted + +accepted + +read zilla:begin.ext ${kafka:matchBeginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_AND_FETCH") + .topic("mqtt-sessions") + .groupId("mqtt-clients") + .filter() + .header("type", "will-signal") + .build() + .filter() + .header("type", "expiry-signal") + .build() + .build() + .build()} + +connected + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .merged() + .deferred(0) + .partition(-1, -1) + .key("client-1#expiry-signal") + .header("type", "expiry-signal") + .build() + .build()} +write ${mqtt:sessionSignal() + .expiry() + .instanceId("zilla-1") + .clientId("client-1") + .delay(1000) + .expireAt(expireAt) + .build() + .build()} +write flush + +# cleanup session state +read zilla:data.ext ${kafka:matchDataEx() + .typeId(zilla:id("kafka")) + .merged() + .deferred(0) + .partition(-1, -1) + .key("client-1") + .hashKey("client-1") + .build() + .build()} +read zilla:data.null diff --git a/specs/binding-mqtt-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/kafka/session.session.expiry.fragmented/client.rpt b/specs/binding-mqtt-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/kafka/session.session.expiry.fragmented/client.rpt index a5e991862a..183ae31507 100644 --- a/specs/binding-mqtt-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/kafka/session.session.expiry.fragmented/client.rpt +++ b/specs/binding-mqtt-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/kafka/session.session.expiry.fragmented/client.rpt @@ -43,14 +43,9 @@ read zilla:data.ext ${kafka:matchDataEx() .header("type", "expiry-signal") .build() .build()} -read ${mqtt:sessionSignal() - .expiry() - .instanceId("zilla-1") - .clientId("client-1") - .delay(2000) - .expireAt(expireAt) - .build() - .build()} +read [0x01 0x07 0x00 0x7a 0x69 0x6c 0x6c 0x61 0x2d 0x31 0x08 0x00 0x63 0x6c 0x69 0x65 0x6e 0x74 0x2d] + +read [0x31 0xd0 0x07 0x00 0x00 0xbe 0x35 0x1a 0xa5 0x8a 0x01 0x00 0x00] read zilla:data.ext ${kafka:matchDataEx() .typeId(zilla:id("kafka")) @@ -63,4 +58,3 @@ read zilla:data.ext ${kafka:matchDataEx() .build() .build()} read zilla:data.null - diff --git a/specs/binding-mqtt-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/kafka/session.session.expiry.fragmented/server.rpt b/specs/binding-mqtt-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/kafka/session.session.expiry.fragmented/server.rpt index 92d6b90b1f..fa11dc4ef9 100644 --- a/specs/binding-mqtt-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/kafka/session.session.expiry.fragmented/server.rpt +++ b/specs/binding-mqtt-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/kafka/session.session.expiry.fragmented/server.rpt @@ -13,14 +13,10 @@ # specific language governing permissions and limitations under the License. # -property delayMillis 2000L -property expireAt ${mqtt:timestamp() + delayMillis} - accept "zilla://streams/kafka0" option zilla:window 8192 option zilla:transmission "duplex" - accepted read zilla:begin.ext ${kafka:matchBeginEx() diff --git a/specs/binding-mqtt-kafka.spec/src/test/java/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/KafkaIT.java b/specs/binding-mqtt-kafka.spec/src/test/java/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/KafkaIT.java index 12a4faa740..6e6328854c 100644 --- a/specs/binding-mqtt-kafka.spec/src/test/java/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/KafkaIT.java +++ b/specs/binding-mqtt-kafka.spec/src/test/java/io/aklivity/zilla/specs/binding/mqtt/kafka/streams/KafkaIT.java @@ -781,4 +781,22 @@ public void shouldRedirect() throws Exception { k3po.finish(); } + + @Test + @Specification({ + "${kafka}/session.session.expiry.fragmented/client", + "${kafka}/session.session.expiry.fragmented/server"}) + public void shouldDecodeSessionExpirySignalFragmented() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${kafka}/session.expiry.after.signal.stream.restart/client", + "${kafka}/session.expiry.after.signal.stream.restart/server"}) + public void shouldExpireSessionAfterSignalStreamRestart() throws Exception + { + k3po.finish(); + } } diff --git a/specs/binding-mqtt.spec/pom.xml b/specs/binding-mqtt.spec/pom.xml index 5f4a8d42f2..6779715e1c 100644 --- a/specs/binding-mqtt.spec/pom.xml +++ b/specs/binding-mqtt.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/binding-proxy.spec/pom.xml b/specs/binding-proxy.spec/pom.xml index 12306c5159..4a9db2913f 100644 --- a/specs/binding-proxy.spec/pom.xml +++ b/specs/binding-proxy.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/binding-sse-kafka.spec/pom.xml b/specs/binding-sse-kafka.spec/pom.xml index eb2a4a7e7d..51c1074e89 100644 --- a/specs/binding-sse-kafka.spec/pom.xml +++ b/specs/binding-sse-kafka.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/binding-sse.spec/pom.xml b/specs/binding-sse.spec/pom.xml index ca154e69d3..66d8e11975 100644 --- a/specs/binding-sse.spec/pom.xml +++ b/specs/binding-sse.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/binding-tcp.spec/pom.xml b/specs/binding-tcp.spec/pom.xml index ddff3d1ef3..c3d560f2ed 100644 --- a/specs/binding-tcp.spec/pom.xml +++ b/specs/binding-tcp.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/binding-tls.spec/pom.xml b/specs/binding-tls.spec/pom.xml index 2e26afe0a2..cfb8f86b73 100644 --- a/specs/binding-tls.spec/pom.xml +++ b/specs/binding-tls.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/binding-ws.spec/pom.xml b/specs/binding-ws.spec/pom.xml index b107141797..afbad2bb8e 100644 --- a/specs/binding-ws.spec/pom.xml +++ b/specs/binding-ws.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/engine.spec/pom.xml b/specs/engine.spec/pom.xml index 58890540d1..07d7331bbf 100644 --- a/specs/engine.spec/pom.xml +++ b/specs/engine.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/exporter-prometheus.spec/pom.xml b/specs/exporter-prometheus.spec/pom.xml index 89ebe02efc..c92655b19c 100644 --- a/specs/exporter-prometheus.spec/pom.xml +++ b/specs/exporter-prometheus.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/guard-jwt.spec/pom.xml b/specs/guard-jwt.spec/pom.xml index 8c692dbaac..d6a1f460db 100644 --- a/specs/guard-jwt.spec/pom.xml +++ b/specs/guard-jwt.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/metrics-grpc.spec/pom.xml b/specs/metrics-grpc.spec/pom.xml index cfd7cef4c9..d47e0b70b1 100644 --- a/specs/metrics-grpc.spec/pom.xml +++ b/specs/metrics-grpc.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/metrics-http.spec/pom.xml b/specs/metrics-http.spec/pom.xml index 1c24c64d7a..8d799fb6ac 100644 --- a/specs/metrics-http.spec/pom.xml +++ b/specs/metrics-http.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/metrics-stream.spec/pom.xml b/specs/metrics-stream.spec/pom.xml index 28b9756740..37ff799137 100644 --- a/specs/metrics-stream.spec/pom.xml +++ b/specs/metrics-stream.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/pom.xml b/specs/pom.xml index b0def5bc9d..929d0cad30 100644 --- a/specs/pom.xml +++ b/specs/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla zilla - 0.9.57 + 0.9.58 ../pom.xml diff --git a/specs/vault-filesystem.spec/pom.xml b/specs/vault-filesystem.spec/pom.xml index 9c26243297..d685d28bd1 100644 --- a/specs/vault-filesystem.spec/pom.xml +++ b/specs/vault-filesystem.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.57 + 0.9.58 ../pom.xml