diff --git a/cloud/docker-image/pom.xml b/cloud/docker-image/pom.xml
index 180f6eb1cd..7f594c2d7f 100644
--- a/cloud/docker-image/pom.xml
+++ b/cloud/docker-image/pom.xml
@@ -223,6 +223,12 @@
${project.version}
runtime
+
+ ${project.groupId}
+ exporter-stdout
+ ${project.version}
+ runtime
+
${project.groupId}
metrics-stream
diff --git a/cloud/docker-image/src/main/docker/zpm.json.template b/cloud/docker-image/src/main/docker/zpm.json.template
index 427e9b3f99..b0df49d630 100644
--- a/cloud/docker-image/src/main/docker/zpm.json.template
+++ b/cloud/docker-image/src/main/docker/zpm.json.template
@@ -43,8 +43,9 @@
"io.aklivity.zilla:command-stop",
"io.aklivity.zilla:command-tune",
"io.aklivity.zilla:engine",
- "io.aklivity.zilla:exporter-prometheus",
"io.aklivity.zilla:exporter-otlp",
+ "io.aklivity.zilla:exporter-prometheus",
+ "io.aklivity.zilla:exporter-stdout",
"io.aklivity.zilla:guard-jwt",
"io.aklivity.zilla:metrics-stream",
"io.aklivity.zilla:metrics-http",
diff --git a/incubator/catalog-schema-registry.spec/pom.xml b/incubator/catalog-schema-registry.spec/pom.xml
index 6e529c8f30..85398279c4 100644
--- a/incubator/catalog-schema-registry.spec/pom.xml
+++ b/incubator/catalog-schema-registry.spec/pom.xml
@@ -63,6 +63,23 @@
org.jasig.maven
maven-notice-plugin
+
+ ${project.groupId}
+ flyweight-maven-plugin
+ ${project.version}
+
+ core schema_registry
+ io.aklivity.zilla.specs.catalog.schema.registry.internal.types
+
+
+
+
+ validate
+ generate
+
+
+
+
com.mycila
license-maven-plugin
@@ -86,6 +103,9 @@
org.jacoco
jacoco-maven-plugin
+
+ io/aklivity/zilla/specs/catalog/schema/registry/internal/types/**/*.class
+
BUNDLE
diff --git a/incubator/catalog-schema-registry.spec/src/main/resources/META-INF/zilla/schema_registry.idl b/incubator/catalog-schema-registry.spec/src/main/resources/META-INF/zilla/schema_registry.idl
new file mode 100644
index 0000000000..df61e3605c
--- /dev/null
+++ b/incubator/catalog-schema-registry.spec/src/main/resources/META-INF/zilla/schema_registry.idl
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+scope schema_registry
+{
+ scope event
+ {
+ enum SchemaRegistryEventType (uint8)
+ {
+ REMOTE_ACCESS_REJECTED (1)
+ }
+
+ struct SchemaRegistryRemoteAccessRejected extends core::event::Event
+ {
+ string8 method;
+ string16 url;
+ int16 status;
+ }
+
+ union SchemaRegistryEvent switch (SchemaRegistryEventType)
+ {
+ case REMOTE_ACCESS_REJECTED: SchemaRegistryRemoteAccessRejected remoteAccessRejected;
+ }
+ }
+}
diff --git a/incubator/catalog-schema-registry/pom.xml b/incubator/catalog-schema-registry/pom.xml
index 74a7b83a1a..22c39b3d02 100644
--- a/incubator/catalog-schema-registry/pom.xml
+++ b/incubator/catalog-schema-registry/pom.xml
@@ -24,7 +24,7 @@
11
11
- 0.90
+ 0.80
0
@@ -71,16 +71,12 @@
org.jasig.maven
maven-notice-plugin
-
- com.mycila
- license-maven-plugin
-
${project.groupId}
flyweight-maven-plugin
${project.version}
- internal
+ core schema_registry internal
io.aklivity.zilla.runtime.catalog.schema.registry.internal.types
@@ -91,6 +87,10 @@
+
+ com.mycila
+ license-maven-plugin
+
maven-checkstyle-plugin
diff --git a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalog.java b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalog.java
index 8affce6dc5..c7ebe83402 100644
--- a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalog.java
+++ b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalog.java
@@ -40,7 +40,7 @@ public String name()
public CatalogContext supply(
EngineContext context)
{
- return new SchemaRegistryCatalogContext();
+ return new SchemaRegistryCatalogContext(context);
}
@Override
diff --git a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogContext.java b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogContext.java
index cf5c8eb7b6..1dbfd3c22f 100644
--- a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogContext.java
+++ b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogContext.java
@@ -15,16 +15,25 @@
package io.aklivity.zilla.runtime.catalog.schema.registry.internal;
import io.aklivity.zilla.runtime.catalog.schema.registry.internal.config.SchemaRegistryOptionsConfig;
+import io.aklivity.zilla.runtime.engine.EngineContext;
import io.aklivity.zilla.runtime.engine.catalog.CatalogContext;
import io.aklivity.zilla.runtime.engine.catalog.CatalogHandler;
import io.aklivity.zilla.runtime.engine.config.CatalogConfig;
public class SchemaRegistryCatalogContext implements CatalogContext
{
+ private final EngineContext context;
+
+ public SchemaRegistryCatalogContext(
+ EngineContext context)
+ {
+ this.context = context;
+ }
+
@Override
public CatalogHandler attach(
CatalogConfig catalog)
{
- return new SchemaRegistryCatalogHandler(SchemaRegistryOptionsConfig.class.cast(catalog.options));
+ return new SchemaRegistryCatalogHandler(SchemaRegistryOptionsConfig.class.cast(catalog.options), context, catalog.id);
}
}
diff --git a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogHandler.java b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogHandler.java
index c8bc750709..88f47fd777 100644
--- a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogHandler.java
+++ b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogHandler.java
@@ -30,6 +30,7 @@
import io.aklivity.zilla.runtime.catalog.schema.registry.internal.config.SchemaRegistryOptionsConfig;
import io.aklivity.zilla.runtime.catalog.schema.registry.internal.serializer.RegisterSchemaRequest;
import io.aklivity.zilla.runtime.catalog.schema.registry.internal.types.SchemaRegistryPrefixFW;
+import io.aklivity.zilla.runtime.engine.EngineContext;
import io.aklivity.zilla.runtime.engine.catalog.CatalogHandler;
import io.aklivity.zilla.runtime.engine.model.function.ValueConsumer;
@@ -51,9 +52,13 @@ public class SchemaRegistryCatalogHandler implements CatalogHandler
private final Int2ObjectCache schemas;
private final Int2ObjectCache schemaIds;
private final long maxAgeMillis;
+ private final SchemaRegistryEventContext event;
+ private final long catalogId;
public SchemaRegistryCatalogHandler(
- SchemaRegistryOptionsConfig config)
+ SchemaRegistryOptionsConfig config,
+ EngineContext context,
+ long catalogId)
{
this.baseUrl = config.url;
this.client = HttpClient.newHttpClient();
@@ -62,6 +67,8 @@ public SchemaRegistryCatalogHandler(
this.schemas = new Int2ObjectCache<>(1, 1024, i -> {});
this.schemaIds = new Int2ObjectCache<>(1, 1024, i -> {});
this.maxAgeMillis = config.maxAge.toMillis();
+ this.event = new SchemaRegistryEventContext(context);
+ this.catalogId = catalogId;
}
@Override
@@ -209,12 +216,18 @@ private String sendHttpRequest(
try
{
- HttpResponse response = client.send(httpRequest, HttpResponse.BodyHandlers.ofString());
- return response.statusCode() == 200 ? response.body() : null;
+ HttpResponse httpResponse = client.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+ boolean success = httpResponse.statusCode() == 200;
+ String responseBody = success ? httpResponse.body() : null;
+ if (!success)
+ {
+ event.remoteAccessRejected(catalogId, httpRequest, httpResponse.statusCode());
+ }
+ return responseBody;
}
catch (Exception ex)
{
- ex.printStackTrace(System.out);
+ event.remoteAccessRejected(catalogId, httpRequest, 0);
}
return null;
}
diff --git a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryEventContext.java b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryEventContext.java
new file mode 100644
index 0000000000..42770a31b1
--- /dev/null
+++ b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryEventContext.java
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.catalog.schema.registry.internal;
+
+import java.net.http.HttpRequest;
+import java.nio.ByteBuffer;
+import java.time.Clock;
+
+import org.agrona.MutableDirectBuffer;
+import org.agrona.concurrent.UnsafeBuffer;
+
+import io.aklivity.zilla.runtime.catalog.schema.registry.internal.types.event.SchemaRegistryEventFW;
+import io.aklivity.zilla.runtime.engine.EngineContext;
+import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer;
+
+public class SchemaRegistryEventContext
+{
+ private static final int EVENT_BUFFER_CAPACITY = 1024;
+
+ private final SchemaRegistryEventFW.Builder schemaRegistryEventRW = new SchemaRegistryEventFW.Builder();
+ private final MutableDirectBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY));
+ private final int schemaRegistryTypeId;
+ private final MessageConsumer eventWriter;
+ private final Clock clock;
+
+ public SchemaRegistryEventContext(
+ EngineContext context)
+ {
+ this.schemaRegistryTypeId = context.supplyTypeId(SchemaRegistryCatalog.NAME);
+ this.eventWriter = context.supplyEventWriter();
+ this.clock = context.clock();
+ }
+
+ public void remoteAccessRejected(
+ long catalogId,
+ HttpRequest httpRequest,
+ int status)
+ {
+ SchemaRegistryEventFW event = schemaRegistryEventRW
+ .wrap(eventBuffer, 0, eventBuffer.capacity())
+ .remoteAccessRejected(e -> e
+ .timestamp(clock.millis())
+ .traceId(0L)
+ .namespacedId(catalogId)
+ .method(httpRequest.method())
+ .url(httpRequest.uri().toString())
+ .status((short) status)
+ )
+ .build();
+ eventWriter.accept(schemaRegistryTypeId, event.buffer(), event.offset(), event.limit());
+ }
+}
diff --git a/incubator/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryIT.java b/incubator/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryIT.java
index f65af539c1..f04ed60d0b 100644
--- a/incubator/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryIT.java
+++ b/incubator/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryIT.java
@@ -20,6 +20,7 @@
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.rules.RuleChain.outerRule;
+import static org.mockito.Mockito.mock;
import java.time.Duration;
@@ -35,6 +36,7 @@
import org.kaazing.k3po.junit.rules.K3poRule;
import io.aklivity.zilla.runtime.catalog.schema.registry.internal.config.SchemaRegistryOptionsConfig;
+import io.aklivity.zilla.runtime.engine.EngineContext;
import io.aklivity.zilla.runtime.engine.catalog.CatalogHandler;
import io.aklivity.zilla.runtime.engine.model.function.ValueConsumer;
@@ -49,6 +51,7 @@ public class SchemaRegistryIT
public final TestRule chain = outerRule(k3po).around(timeout);
private SchemaRegistryOptionsConfig config;
+ private EngineContext context = mock(EngineContext.class);
@Before
public void setup()
@@ -69,7 +72,7 @@ public void shouldResolveSchemaViaSchemaId() throws Exception
"{\"name\":\"status\",\"type\":\"string\"}]," +
"\"name\":\"Event\",\"namespace\":\"io.aklivity.example\",\"type\":\"record\"}";
- SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config);
+ SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L);
String schema = catalog.resolve(9);
@@ -88,7 +91,7 @@ public void shouldResolveSchemaViaSubjectVersion() throws Exception
"{\"name\":\"status\",\"type\":\"string\"}]," +
"\"name\":\"Event\",\"namespace\":\"io.aklivity.example\",\"type\":\"record\"}";
- SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config);
+ SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L);
int schemaId = catalog.resolve("items-snapshots-value", "latest");
@@ -109,7 +112,7 @@ public void shouldRegisterSchema() throws Exception
String schema = "{\"type\": \"record\",\"name\": \"test\",\"fields\":[{\"type\": \"string\",\"name\": \"field1\"}," +
"{\"type\": \"com.acme.Referenced\",\"name\": \"int\"}]}";
- SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config);
+ SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L);
int schemaId = catalog.register("items-snapshots-value", "avro", schema);
@@ -128,7 +131,7 @@ public void shouldResolveSchemaViaSchemaIdFromCache() throws Exception
"{\"name\":\"status\",\"type\":\"string\"}]," +
"\"name\":\"Event\",\"namespace\":\"io.aklivity.example\",\"type\":\"record\"}";
- SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config);
+ SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L);
catalog.resolve(9);
@@ -149,7 +152,7 @@ public void shouldResolveSchemaViaSubjectVersionFromCache() throws Exception
"{\"name\":\"status\",\"type\":\"string\"}]," +
"\"name\":\"Event\",\"namespace\":\"io.aklivity.example\",\"type\":\"record\"}";
- SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config);
+ SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L);
catalog.resolve(catalog.resolve("items-snapshots-value", "latest"));
@@ -167,7 +170,7 @@ public void shouldResolveSchemaViaSubjectVersionFromCache() throws Exception
@Test
public void shouldVerifyMaxPadding()
{
- SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config);
+ SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L);
assertEquals(5, catalog.encodePadding());
}
@@ -175,7 +178,7 @@ public void shouldVerifyMaxPadding()
@Test
public void shouldVerifyEncodedData()
{
- SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config);
+ SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L);
DirectBuffer data = new UnsafeBuffer();
@@ -191,7 +194,7 @@ public void shouldVerifyEncodedData()
public void shouldResolveSchemaIdAndProcessData()
{
- SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config);
+ SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L);
DirectBuffer data = new UnsafeBuffer();
@@ -207,7 +210,7 @@ public void shouldResolveSchemaIdAndProcessData()
@Test
public void shouldResolveSchemaIdFromData()
{
- SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config);
+ SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L);
DirectBuffer data = new UnsafeBuffer();
diff --git a/incubator/exporter-stdout.spec/COPYRIGHT b/incubator/exporter-stdout.spec/COPYRIGHT
new file mode 100644
index 0000000000..0cb10b6f62
--- /dev/null
+++ b/incubator/exporter-stdout.spec/COPYRIGHT
@@ -0,0 +1,12 @@
+Copyright ${copyrightYears} 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.
diff --git a/incubator/exporter-stdout.spec/LICENSE b/incubator/exporter-stdout.spec/LICENSE
new file mode 100644
index 0000000000..f3cf11c3ad
--- /dev/null
+++ b/incubator/exporter-stdout.spec/LICENSE
@@ -0,0 +1,114 @@
+ Aklivity Community License Agreement
+ Version 1.0
+
+This Aklivity Community License Agreement Version 1.0 (the “Agreement”) sets
+forth the terms on which Aklivity, Inc. (“Aklivity”) makes available certain
+software made available by Aklivity under this Agreement (the “Software”). BY
+INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF THE SOFTWARE,
+YOU AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO
+SUCH TERMS AND CONDITIONS, YOU MUST NOT USE THE SOFTWARE. IF YOU ARE RECEIVING
+THE SOFTWARE ON BEHALF OF A LEGAL ENTITY, YOU REPRESENT AND WARRANT THAT YOU
+HAVE THE ACTUAL AUTHORITY TO AGREE TO THE TERMS AND CONDITIONS OF THIS
+AGREEMENT ON BEHALF OF SUCH ENTITY. “Licensee” means you, an individual, or
+the entity on whose behalf you are receiving the Software.
+
+ 1. LICENSE GRANT AND CONDITIONS.
+
+ 1.1 License. Subject to the terms and conditions of this Agreement,
+ Aklivity hereby grants to Licensee a non-exclusive, royalty-free,
+ worldwide, non-transferable, non-sublicenseable license during the term
+ of this Agreement to: (a) use the Software; (b) prepare modifications and
+ derivative works of the Software; (c) distribute the Software (including
+ without limitation in source code or object code form); and (d) reproduce
+ copies of the Software (the “License”). Licensee is not granted the
+ right to, and Licensee shall not, exercise the License for an Excluded
+ Purpose. For purposes of this Agreement, “Excluded Purpose” means making
+ available any software-as-a-service, platform-as-a-service,
+ infrastructure-as-a-service or other similar online service that competes
+ with Aklivity products or services that provide the Software.
+
+ 1.2 Conditions. In consideration of the License, Licensee’s distribution
+ of the Software is subject to the following conditions:
+
+ (a) Licensee must cause any Software modified by Licensee to carry
+ prominent notices stating that Licensee modified the Software.
+
+ (b) On each Software copy, Licensee shall reproduce and not remove or
+ alter all Aklivity or third party copyright or other proprietary
+ notices contained in the Software, and Licensee must provide the
+ notice below with each copy.
+
+ “This software is made available by Aklivity, Inc., under the
+ terms of the Aklivity Community License Agreement, Version 1.0
+ located at http://www.Aklivity.io/Aklivity-community-license. BY
+ INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF
+ THE SOFTWARE, YOU AGREE TO THE TERMS OF SUCH LICENSE AGREEMENT.”
+
+ 1.3 Licensee Modifications. Licensee may add its own copyright notices
+ to modifications made by Licensee and may provide additional or different
+ license terms and conditions for use, reproduction, or distribution of
+ Licensee’s modifications. While redistributing the Software or
+ modifications thereof, Licensee may choose to offer, for a fee or free of
+ charge, support, warranty, indemnity, or other obligations. Licensee, and
+ not Aklivity, will be responsible for any such obligations.
+
+ 1.4 No Sublicensing. The License does not include the right to
+ sublicense the Software, however, each recipient to which Licensee
+ provides the Software may exercise the Licenses so long as such recipient
+ agrees to the terms and conditions of this Agreement.
+
+ 2. TERM AND TERMINATION. This Agreement will continue unless and until
+ earlier terminated as set forth herein. If Licensee breaches any of its
+ conditions or obligations under this Agreement, this Agreement will
+ terminate automatically and the License will terminate automatically and
+ permanently.
+
+ 3. INTELLECTUAL PROPERTY. As between the parties, Aklivity will retain all
+ right, title, and interest in the Software, and all intellectual property
+ rights therein. Aklivity hereby reserves all rights not expressly granted
+ to Licensee in this Agreement. Aklivity hereby reserves all rights in its
+ trademarks and service marks, and no licenses therein are granted in this
+ Agreement.
+
+ 4. DISCLAIMER. Aklivity HEREBY DISCLAIMS ANY AND ALL WARRANTIES AND
+ CONDITIONS, EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, AND SPECIFICALLY
+ DISCLAIMS ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR
+ PURPOSE, WITH RESPECT TO THE SOFTWARE.
+
+ 5. LIMITATION OF LIABILITY. Aklivity WILL NOT BE LIABLE FOR ANY DAMAGES OF
+ ANY KIND, INCLUDING BUT NOT LIMITED TO, LOST PROFITS OR ANY CONSEQUENTIAL,
+ SPECIAL, INCIDENTAL, INDIRECT, OR DIRECT DAMAGES, HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, ARISING OUT OF THIS AGREEMENT. THE FOREGOING SHALL
+ APPLY TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+
+ 6.GENERAL.
+
+ 6.1 Governing Law. This Agreement will be governed by and interpreted in
+ accordance with the laws of the state of California, without reference to
+ its conflict of laws principles. If Licensee is located within the
+ United States, all disputes arising out of this Agreement are subject to
+ the exclusive jurisdiction of courts located in Santa Clara County,
+ California. USA. If Licensee is located outside of the United States,
+ any dispute, controversy or claim arising out of or relating to this
+ Agreement will be referred to and finally determined by arbitration in
+ accordance with the JAMS International Arbitration Rules. The tribunal
+ will consist of one arbitrator. The place of arbitration will be Palo
+ Alto, California. The language to be used in the arbitral proceedings
+ will be English. Judgment upon the award rendered by the arbitrator may
+ be entered in any court having jurisdiction thereof.
+
+ 6.2 Assignment. Licensee is not authorized to assign its rights under
+ this Agreement to any third party. Aklivity may freely assign its rights
+ under this Agreement to any third party.
+
+ 6.3 Other. This Agreement is the entire agreement between the parties
+ regarding the subject matter hereof. No amendment or modification of
+ this Agreement will be valid or binding upon the parties unless made in
+ writing and signed by the duly authorized representatives of both
+ parties. In the event that any provision, including without limitation
+ any condition, of this Agreement is held to be unenforceable, this
+ Agreement and all licenses and rights granted hereunder will immediately
+ terminate. Waiver by Aklivity of a breach of any provision of this
+ Agreement or the failure by Aklivity to exercise any right hereunder
+ will not be construed as a waiver of any subsequent breach of that right
+ or as a waiver of any other right.
\ No newline at end of file
diff --git a/incubator/exporter-stdout.spec/NOTICE b/incubator/exporter-stdout.spec/NOTICE
new file mode 100644
index 0000000000..cf99769e41
--- /dev/null
+++ b/incubator/exporter-stdout.spec/NOTICE
@@ -0,0 +1,18 @@
+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.
+
+This project includes:
+ agrona under The Apache License, Version 2.0
+ ICU4J under Unicode/ICU License
+ Jakarta JSON Processing API under Eclipse Public License 2.0 or GNU General Public License, version 2 with the GNU Classpath Exception
+ org.leadpony.justify under The Apache Software License, Version 2.0
+ zilla::specs::engine.spec under The Apache Software License, Version 2.0
+
diff --git a/incubator/exporter-stdout.spec/NOTICE.template b/incubator/exporter-stdout.spec/NOTICE.template
new file mode 100644
index 0000000000..209ca12f74
--- /dev/null
+++ b/incubator/exporter-stdout.spec/NOTICE.template
@@ -0,0 +1,13 @@
+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.
+
+This project includes:
+#GENERATED_NOTICES#
diff --git a/incubator/exporter-stdout.spec/mvnw b/incubator/exporter-stdout.spec/mvnw
new file mode 100755
index 0000000000..d2f0ea3808
--- /dev/null
+++ b/incubator/exporter-stdout.spec/mvnw
@@ -0,0 +1,310 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF 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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven2 Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/incubator/exporter-stdout.spec/mvnw.cmd b/incubator/exporter-stdout.spec/mvnw.cmd
new file mode 100644
index 0000000000..b26ab24f03
--- /dev/null
+++ b/incubator/exporter-stdout.spec/mvnw.cmd
@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven2 Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/incubator/exporter-stdout.spec/pom.xml b/incubator/exporter-stdout.spec/pom.xml
new file mode 100644
index 0000000000..1b07db88bf
--- /dev/null
+++ b/incubator/exporter-stdout.spec/pom.xml
@@ -0,0 +1,180 @@
+
+
+
+ 4.0.0
+
+ io.aklivity.zilla
+ specs
+ develop-SNAPSHOT
+ ../pom.xml
+
+
+ exporter-stdout.spec
+ zilla::incubator::exporter-stdout.spec
+
+
+
+ Aklivity Community License Agreement
+ https://www.aklivity.io/aklivity-community-license/
+ repo
+
+
+
+
+ 11
+ 11
+ 1.00
+ 0
+
+
+
+
+ org.kaazing
+ k3po.lang
+ provided
+
+
+ ${project.groupId}
+ engine.spec
+ ${project.version}
+
+
+ ${project.groupId}
+ binding-http.spec
+ ${project.version}
+ test
+
+
+ ${project.groupId}
+ binding-proxy.spec
+ ${project.version}
+ test
+
+
+ junit
+ junit
+ test
+
+
+ org.kaazing
+ k3po.junit
+ test
+
+
+ org.hamcrest
+ hamcrest-library
+ test
+
+
+
+
+
+
+ src/main/resources
+
+
+ src/main/scripts
+
+
+
+
+
+ org.jasig.maven
+ maven-notice-plugin
+
+
+ ${project.groupId}
+ flyweight-maven-plugin
+ ${project.version}
+
+ core
+ io.aklivity.zilla.specs.exporter.stdout.internal.types
+
+
+
+
+ validate
+ generate
+
+
+
+
+
+ com.mycila
+ license-maven-plugin
+
+
+ **/keys
+ **/trust
+ **/signers
+
+
+
+
+ maven-checkstyle-plugin
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ org.moditect
+ moditect-maven-plugin
+
+
+ org.kaazing
+ k3po-maven-plugin
+
+
+ ${project.groupId}
+ engine
+ ${project.version}
+ test-jar
+
+
+ ${project.groupId}
+ engine
+ ${project.version}
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+ org.jacoco
+ jacoco-maven-plugin
+
+
+ io/aklivity/zilla/specs/exporter/stdout/internal/types/**/*.class
+
+
+
+ BUNDLE
+
+
+ INSTRUCTION
+ COVEREDRATIO
+ ${jacoco.coverage.ratio}
+
+
+ CLASS
+ MISSEDCOUNT
+ ${jacoco.missed.count}
+
+
+
+
+
+
+
+
+
diff --git a/incubator/exporter-stdout.spec/src/main/moditect/module-info.java b/incubator/exporter-stdout.spec/src/main/moditect/module-info.java
new file mode 100644
index 0000000000..9b8473aaae
--- /dev/null
+++ b/incubator/exporter-stdout.spec/src/main/moditect/module-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+open module io.aklivity.zilla.specs.exporter.stdout
+{
+ requires transitive io.aklivity.zilla.specs.engine;
+}
diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.authorization.credentials.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.authorization.credentials.yaml
new file mode 100644
index 0000000000..a6daa035f9
--- /dev/null
+++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.authorization.credentials.yaml
@@ -0,0 +1,53 @@
+#
+# 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.
+#
+
+---
+name: test
+telemetry:
+ exporters:
+ stdout0:
+ type: stdout
+guards:
+ jwt0:
+ type: jwt
+ options:
+ issuer: https://auth.example.com
+ audience: https://api.example.com
+ keys:
+ - kty: RSA
+ n: qqEu50hX+43Bx4W1UYWnAVKwFm+vDbP0kuIOSLVNa+HKQdHTf+3Sei5UCnkskn796izA29D0DdCy3ET9oaKRHIJyKbqFl0rv6f516QzOoXKC6N01sXBHBE/ovs0wwDvlaW+gFGPgkzdcfUlyrWLDnLV7LcuQymhTND2uH0oR3wJnNENN/OFgM1KGPPDOe19YsIKdLqARgxrhZVsh06OurEviZTXOBFI5r+yac7haDwOQhLHXNv+Y9MNvxs5QLWPFIM3bNUWfYrJnLrs4hGJS+y/KDM9Si+HL30QAFXy4YNO33J8DHjZ7ddG5n8/FqplOKvRtUgjcKWlxoGY4VdVaDQ==
+ e: AQAB
+ alg: RS256
+ kid: example
+bindings:
+ net0:
+ type: http
+ kind: server
+ options:
+ versions:
+ - http/1.1
+ authorization:
+ jwt0:
+ credentials:
+ cookies:
+ access_token: "{credentials}"
+ headers:
+ authorization: Bearer {credentials}
+ query:
+ access_token: "{credentials}"
+ routes:
+ - exit: app0
+ guarded:
+ jwt0: []
diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.yaml
new file mode 100644
index 0000000000..7202d45307
--- /dev/null
+++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.yaml
@@ -0,0 +1,33 @@
+#
+# 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.
+#
+
+---
+name: test
+telemetry:
+ exporters:
+ stdout0:
+ type: stdout
+bindings:
+ net0:
+ type: http
+ kind: server
+ options:
+ versions:
+ - http/1.1
+ routes:
+ - exit: app0
+ when:
+ - headers:
+ :authority: localhost:8080
diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v2/server.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v2/server.yaml
new file mode 100644
index 0000000000..effa3e4a6f
--- /dev/null
+++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v2/server.yaml
@@ -0,0 +1,33 @@
+#
+# 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.
+#
+
+---
+name: test
+telemetry:
+ exporters:
+ stdout0:
+ type: stdout
+bindings:
+ net0:
+ type: http
+ kind: server
+ options:
+ versions:
+ - h2
+ routes:
+ - exit: app0
+ when:
+ - headers:
+ :authority: localhost:8080
diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/config/client.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/config/client.yaml
new file mode 100644
index 0000000000..ff4c4a3aea
--- /dev/null
+++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/config/client.yaml
@@ -0,0 +1,26 @@
+#
+# 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.
+#
+
+---
+name: test
+telemetry:
+ exporters:
+ stdout0:
+ type: stdout
+bindings:
+ app0:
+ type: kafka
+ kind: client
+ exit: net0
diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tcp/config/client.host.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tcp/config/client.host.yaml
new file mode 100644
index 0000000000..04191ddc52
--- /dev/null
+++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tcp/config/client.host.yaml
@@ -0,0 +1,28 @@
+#
+# 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.
+#
+
+---
+name: test
+telemetry:
+ exporters:
+ stdout0:
+ type: stdout
+bindings:
+ app0:
+ type: tcp
+ kind: client
+ options:
+ host: localhost
+ port: 8080
diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/server.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/server.yaml
new file mode 100644
index 0000000000..fd7e4b90a0
--- /dev/null
+++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/server.yaml
@@ -0,0 +1,38 @@
+#
+# 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.
+#
+
+---
+name: test
+telemetry:
+ exporters:
+ stdout0:
+ type: stdout
+vaults:
+ server:
+ type: filesystem
+ options:
+ keys:
+ store: stores/server/keys
+ type: pkcs12
+ password: generated
+bindings:
+ net0:
+ type: tls
+ kind: server
+ vault: server
+ options:
+ keys:
+ - localhost
+ exit: app0
diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/.gitignore b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/.gitignore
new file mode 100644
index 0000000000..507484f3e9
--- /dev/null
+++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/.gitignore
@@ -0,0 +1,2 @@
+*.crt
+*.csr
diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/keys b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/keys
new file mode 100644
index 0000000000..d2fa3da9b9
Binary files /dev/null and b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/keys differ
diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/signers b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/signers
new file mode 100644
index 0000000000..58e72c4460
Binary files /dev/null and b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/signers differ
diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/trust b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/trust
new file mode 100644
index 0000000000..bb624ca8c8
Binary files /dev/null and b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/trust differ
diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/keys b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/keys
new file mode 100644
index 0000000000..e10b65832b
Binary files /dev/null and b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/keys differ
diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/signers b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/signers
new file mode 100644
index 0000000000..0c8e698ab1
Binary files /dev/null and b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/signers differ
diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/trust b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/trust
new file mode 100644
index 0000000000..377ed0c4c4
Binary files /dev/null and b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/trust differ
diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/stdout/schema/stdout.schema.patch.json b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/stdout/schema/stdout.schema.patch.json
new file mode 100644
index 0000000000..f3f7450945
--- /dev/null
+++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/stdout/schema/stdout.schema.patch.json
@@ -0,0 +1,34 @@
+[
+ {
+ "op": "add",
+ "path": "/$defs/telemetry/exporter/properties/type/enum/-",
+ "value": "stdout"
+ },
+ {
+ "op": "add",
+ "path": "/$defs/telemetry/exporter/allOf/-",
+ "value":
+ {
+ "if":
+ {
+ "properties":
+ {
+ "type":
+ {
+ "const": "stdout"
+ }
+ }
+ },
+ "then":
+ {
+ "properties":
+ {
+ "type":
+ {
+ "const": "stdout"
+ }
+ }
+ }
+ }
+ }
+]
diff --git a/incubator/exporter-stdout/COPYRIGHT b/incubator/exporter-stdout/COPYRIGHT
new file mode 100644
index 0000000000..0cb10b6f62
--- /dev/null
+++ b/incubator/exporter-stdout/COPYRIGHT
@@ -0,0 +1,12 @@
+Copyright ${copyrightYears} 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.
diff --git a/incubator/exporter-stdout/LICENSE b/incubator/exporter-stdout/LICENSE
new file mode 100644
index 0000000000..f3cf11c3ad
--- /dev/null
+++ b/incubator/exporter-stdout/LICENSE
@@ -0,0 +1,114 @@
+ Aklivity Community License Agreement
+ Version 1.0
+
+This Aklivity Community License Agreement Version 1.0 (the “Agreement”) sets
+forth the terms on which Aklivity, Inc. (“Aklivity”) makes available certain
+software made available by Aklivity under this Agreement (the “Software”). BY
+INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF THE SOFTWARE,
+YOU AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO
+SUCH TERMS AND CONDITIONS, YOU MUST NOT USE THE SOFTWARE. IF YOU ARE RECEIVING
+THE SOFTWARE ON BEHALF OF A LEGAL ENTITY, YOU REPRESENT AND WARRANT THAT YOU
+HAVE THE ACTUAL AUTHORITY TO AGREE TO THE TERMS AND CONDITIONS OF THIS
+AGREEMENT ON BEHALF OF SUCH ENTITY. “Licensee” means you, an individual, or
+the entity on whose behalf you are receiving the Software.
+
+ 1. LICENSE GRANT AND CONDITIONS.
+
+ 1.1 License. Subject to the terms and conditions of this Agreement,
+ Aklivity hereby grants to Licensee a non-exclusive, royalty-free,
+ worldwide, non-transferable, non-sublicenseable license during the term
+ of this Agreement to: (a) use the Software; (b) prepare modifications and
+ derivative works of the Software; (c) distribute the Software (including
+ without limitation in source code or object code form); and (d) reproduce
+ copies of the Software (the “License”). Licensee is not granted the
+ right to, and Licensee shall not, exercise the License for an Excluded
+ Purpose. For purposes of this Agreement, “Excluded Purpose” means making
+ available any software-as-a-service, platform-as-a-service,
+ infrastructure-as-a-service or other similar online service that competes
+ with Aklivity products or services that provide the Software.
+
+ 1.2 Conditions. In consideration of the License, Licensee’s distribution
+ of the Software is subject to the following conditions:
+
+ (a) Licensee must cause any Software modified by Licensee to carry
+ prominent notices stating that Licensee modified the Software.
+
+ (b) On each Software copy, Licensee shall reproduce and not remove or
+ alter all Aklivity or third party copyright or other proprietary
+ notices contained in the Software, and Licensee must provide the
+ notice below with each copy.
+
+ “This software is made available by Aklivity, Inc., under the
+ terms of the Aklivity Community License Agreement, Version 1.0
+ located at http://www.Aklivity.io/Aklivity-community-license. BY
+ INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF
+ THE SOFTWARE, YOU AGREE TO THE TERMS OF SUCH LICENSE AGREEMENT.”
+
+ 1.3 Licensee Modifications. Licensee may add its own copyright notices
+ to modifications made by Licensee and may provide additional or different
+ license terms and conditions for use, reproduction, or distribution of
+ Licensee’s modifications. While redistributing the Software or
+ modifications thereof, Licensee may choose to offer, for a fee or free of
+ charge, support, warranty, indemnity, or other obligations. Licensee, and
+ not Aklivity, will be responsible for any such obligations.
+
+ 1.4 No Sublicensing. The License does not include the right to
+ sublicense the Software, however, each recipient to which Licensee
+ provides the Software may exercise the Licenses so long as such recipient
+ agrees to the terms and conditions of this Agreement.
+
+ 2. TERM AND TERMINATION. This Agreement will continue unless and until
+ earlier terminated as set forth herein. If Licensee breaches any of its
+ conditions or obligations under this Agreement, this Agreement will
+ terminate automatically and the License will terminate automatically and
+ permanently.
+
+ 3. INTELLECTUAL PROPERTY. As between the parties, Aklivity will retain all
+ right, title, and interest in the Software, and all intellectual property
+ rights therein. Aklivity hereby reserves all rights not expressly granted
+ to Licensee in this Agreement. Aklivity hereby reserves all rights in its
+ trademarks and service marks, and no licenses therein are granted in this
+ Agreement.
+
+ 4. DISCLAIMER. Aklivity HEREBY DISCLAIMS ANY AND ALL WARRANTIES AND
+ CONDITIONS, EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, AND SPECIFICALLY
+ DISCLAIMS ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR
+ PURPOSE, WITH RESPECT TO THE SOFTWARE.
+
+ 5. LIMITATION OF LIABILITY. Aklivity WILL NOT BE LIABLE FOR ANY DAMAGES OF
+ ANY KIND, INCLUDING BUT NOT LIMITED TO, LOST PROFITS OR ANY CONSEQUENTIAL,
+ SPECIAL, INCIDENTAL, INDIRECT, OR DIRECT DAMAGES, HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, ARISING OUT OF THIS AGREEMENT. THE FOREGOING SHALL
+ APPLY TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+
+ 6.GENERAL.
+
+ 6.1 Governing Law. This Agreement will be governed by and interpreted in
+ accordance with the laws of the state of California, without reference to
+ its conflict of laws principles. If Licensee is located within the
+ United States, all disputes arising out of this Agreement are subject to
+ the exclusive jurisdiction of courts located in Santa Clara County,
+ California. USA. If Licensee is located outside of the United States,
+ any dispute, controversy or claim arising out of or relating to this
+ Agreement will be referred to and finally determined by arbitration in
+ accordance with the JAMS International Arbitration Rules. The tribunal
+ will consist of one arbitrator. The place of arbitration will be Palo
+ Alto, California. The language to be used in the arbitral proceedings
+ will be English. Judgment upon the award rendered by the arbitrator may
+ be entered in any court having jurisdiction thereof.
+
+ 6.2 Assignment. Licensee is not authorized to assign its rights under
+ this Agreement to any third party. Aklivity may freely assign its rights
+ under this Agreement to any third party.
+
+ 6.3 Other. This Agreement is the entire agreement between the parties
+ regarding the subject matter hereof. No amendment or modification of
+ this Agreement will be valid or binding upon the parties unless made in
+ writing and signed by the duly authorized representatives of both
+ parties. In the event that any provision, including without limitation
+ any condition, of this Agreement is held to be unenforceable, this
+ Agreement and all licenses and rights granted hereunder will immediately
+ terminate. Waiver by Aklivity of a breach of any provision of this
+ Agreement or the failure by Aklivity to exercise any right hereunder
+ will not be construed as a waiver of any subsequent breach of that right
+ or as a waiver of any other right.
\ No newline at end of file
diff --git a/incubator/exporter-stdout/NOTICE b/incubator/exporter-stdout/NOTICE
new file mode 100644
index 0000000000..9024d8926d
--- /dev/null
+++ b/incubator/exporter-stdout/NOTICE
@@ -0,0 +1,13 @@
+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.
+
+This project includes:
+
diff --git a/incubator/exporter-stdout/NOTICE.template b/incubator/exporter-stdout/NOTICE.template
new file mode 100644
index 0000000000..209ca12f74
--- /dev/null
+++ b/incubator/exporter-stdout/NOTICE.template
@@ -0,0 +1,13 @@
+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.
+
+This project includes:
+#GENERATED_NOTICES#
diff --git a/incubator/exporter-stdout/mvnw b/incubator/exporter-stdout/mvnw
new file mode 100755
index 0000000000..d2f0ea3808
--- /dev/null
+++ b/incubator/exporter-stdout/mvnw
@@ -0,0 +1,310 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF 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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven2 Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/incubator/exporter-stdout/mvnw.cmd b/incubator/exporter-stdout/mvnw.cmd
new file mode 100644
index 0000000000..b26ab24f03
--- /dev/null
+++ b/incubator/exporter-stdout/mvnw.cmd
@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven2 Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/incubator/exporter-stdout/pom.xml b/incubator/exporter-stdout/pom.xml
new file mode 100644
index 0000000000..58bf155c90
--- /dev/null
+++ b/incubator/exporter-stdout/pom.xml
@@ -0,0 +1,340 @@
+
+
+
+ 4.0.0
+
+ io.aklivity.zilla
+ incubator
+ develop-SNAPSHOT
+ ../pom.xml
+
+
+ exporter-stdout
+ zilla::incubator::exporter-stdout
+
+
+
+ Aklivity Community License Agreement
+ https://www.aklivity.io/aklivity-community-license/
+ repo
+
+
+
+
+ 11
+ 11
+ 0.75
+ 0
+
+
+
+
+ ${project.groupId}
+ exporter-stdout.spec
+ ${project.version}
+ provided
+
+
+ ${project.groupId}
+ engine
+ ${project.version}
+ provided
+
+
+ ${project.groupId}
+ binding-http.spec
+ ${project.version}
+ provided
+
+
+ ${project.groupId}
+ binding-kafka.spec
+ ${project.version}
+ provided
+
+
+ ${project.groupId}
+ binding-tcp.spec
+ ${project.version}
+ provided
+
+
+ ${project.groupId}
+ binding-tls.spec
+ ${project.version}
+ provided
+
+
+ ${project.groupId}
+ catalog-schema-registry.spec
+ ${project.version}
+ provided
+
+
+ ${project.groupId}
+ guard-jwt.spec
+ ${project.version}
+ provided
+
+
+ ${project.groupId}
+ vault-filesystem.spec
+ ${project.version}
+ provided
+
+
+ ${project.groupId}
+ engine
+ test-jar
+ ${project.version}
+ test
+
+
+ ${project.groupId}
+ binding-http
+ ${project.version}
+ test
+
+
+ ${project.groupId}
+ binding-kafka
+ ${project.version}
+ test
+
+
+ ${project.groupId}
+ binding-tcp
+ ${project.version}
+ test
+
+
+ ${project.groupId}
+ binding-tls
+ ${project.version}
+ test
+
+
+ ${project.groupId}
+ catalog-schema-registry
+ ${project.version}
+ test
+
+
+ ${project.groupId}
+ guard-jwt
+ ${project.version}
+ test
+
+
+ ${project.groupId}
+ model-json
+ ${project.version}
+ test
+
+
+ ${project.groupId}
+ vault-filesystem
+ ${project.version}
+ test
+
+
+ junit
+ junit
+ test
+
+
+ org.hamcrest
+ hamcrest
+ test
+
+
+ com.vtence.hamcrest
+ hamcrest-jpa
+ test
+
+
+ com.github.npathai
+ hamcrest-optional
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ org.kaazing
+ k3po.junit
+ test
+
+
+ org.kaazing
+ k3po.lang
+ test
+
+
+ org.openjdk.jmh
+ jmh-core
+ test
+
+
+ org.openjdk.jmh
+ jmh-generator-annprocess
+ test
+
+
+
+
+
+
+ org.jasig.maven
+ maven-notice-plugin
+
+
+ ${project.groupId}
+ flyweight-maven-plugin
+ ${project.version}
+
+ core http jwt kafka schema_registry tcp tls
+ io.aklivity.zilla.runtime.exporter.stdout.internal.types
+
+
+
+
+ generate
+
+
+
+
+
+ com.mycila
+ license-maven-plugin
+
+
+ **/org.mockito.plugins.MockMaker
+
+
+
+
+ maven-checkstyle-plugin
+
+
+ maven-dependency-plugin
+
+
+ process-resources
+
+ unpack
+
+
+
+
+ ${project.groupId}
+ exporter-stdout.spec
+
+
+ ^\Qio/aklivity/zilla/specs/exporter/stdout/\E
+ io/aklivity/zilla/runtime/exporter/stdout/internal/
+
+
+
+
+ io/aklivity/zilla/specs/exporter/stdout/schema/stdout.schema.patch.json
+ ${project.build.directory}/classes
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ org.moditect
+ moditect-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+
+ test-jar
+
+
+
+
+
+ org.kaazing
+ k3po-maven-plugin
+
+
+ ${project.groupId}
+ engine
+ ${project.version}
+ test-jar
+
+
+ ${project.groupId}
+ engine
+ ${project.version}
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+ org.jacoco
+ jacoco-maven-plugin
+
+
+ io/aklivity/zilla/runtime/exporter/stdout/internal/types/**/*.class
+
+
+
+ BUNDLE
+
+
+ INSTRUCTION
+ COVEREDRATIO
+ ${jacoco.coverage.ratio}
+
+
+ CLASS
+ MISSEDCOUNT
+ ${jacoco.missed.count}
+
+
+
+
+
+
+
+ io.gatling
+ maven-shade-plugin
+
+
+
+ org.agrona:agrona
+ io.aklivity.zilla:engine
+ org.openjdk.jmh:jmh-core
+ net.sf.jopt-simple:jopt-simple
+ org.apache.commons:commons-math3
+ commons-cli:commons-cli
+ com.github.biboudis:jmh-profilers
+
+
+
+
+
+
+
diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutConfiguration.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutConfiguration.java
new file mode 100644
index 0000000000..bbc9cfb038
--- /dev/null
+++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutConfiguration.java
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal;
+
+import java.io.PrintStream;
+import java.lang.reflect.Field;
+
+import org.agrona.LangUtil;
+
+import io.aklivity.zilla.runtime.engine.Configuration;
+
+public class StdoutConfiguration extends Configuration
+{
+ private static final ConfigurationDef STDOUT_CONFIG;
+
+ public static final PropertyDef STDOUT_OUTPUT;
+
+ static
+ {
+ final ConfigurationDef config = new ConfigurationDef("zilla.exporter.stdout");
+ STDOUT_OUTPUT = config.property(PrintStream.class, "output",
+ StdoutConfiguration::decodeOutput, c -> System.out);
+ STDOUT_CONFIG = config;
+ }
+
+ public StdoutConfiguration(
+ Configuration config)
+ {
+ super(STDOUT_CONFIG, config);
+ }
+
+ public PrintStream output()
+ {
+ return STDOUT_OUTPUT.get(this);
+ }
+
+ private static PrintStream decodeOutput(
+ Configuration config,
+ String value)
+ {
+ try
+ {
+ int fieldAt = value.lastIndexOf(".");
+ Class> ownerClass = Class.forName(value.substring(0, fieldAt));
+ String fieldName = value.substring(fieldAt + 1);
+ Field field = ownerClass.getDeclaredField(fieldName);
+ return (PrintStream) field.get(null);
+ }
+ catch (Throwable ex)
+ {
+ LangUtil.rethrowUnchecked(ex);
+ }
+ return null;
+ }
+}
diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporter.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporter.java
new file mode 100644
index 0000000000..3e606c2d64
--- /dev/null
+++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporter.java
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal;
+
+import java.net.URL;
+
+import io.aklivity.zilla.runtime.engine.EngineContext;
+import io.aklivity.zilla.runtime.engine.exporter.Exporter;
+import io.aklivity.zilla.runtime.engine.exporter.ExporterContext;
+
+public class StdoutExporter implements Exporter
+{
+ public static final String NAME = "stdout";
+
+ private final StdoutConfiguration config;
+
+ public StdoutExporter(
+ StdoutConfiguration config)
+ {
+ this.config = config;
+ }
+
+ @Override
+ public String name()
+ {
+ return NAME;
+ }
+
+ @Override
+ public URL type()
+ {
+ return getClass().getResource("schema/stdout.schema.patch.json");
+ }
+
+ @Override
+ public ExporterContext supply(
+ EngineContext context)
+ {
+ return new StdoutExporterContext(config, context);
+ }
+}
diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterContext.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterContext.java
new file mode 100644
index 0000000000..bb8d7d3adb
--- /dev/null
+++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterContext.java
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal;
+
+import java.util.List;
+import java.util.function.LongFunction;
+
+import io.aklivity.zilla.runtime.engine.EngineContext;
+import io.aklivity.zilla.runtime.engine.binding.function.MessageReader;
+import io.aklivity.zilla.runtime.engine.config.AttributeConfig;
+import io.aklivity.zilla.runtime.engine.config.ExporterConfig;
+import io.aklivity.zilla.runtime.engine.config.KindConfig;
+import io.aklivity.zilla.runtime.engine.exporter.ExporterContext;
+import io.aklivity.zilla.runtime.engine.exporter.ExporterHandler;
+import io.aklivity.zilla.runtime.engine.metrics.Collector;
+import io.aklivity.zilla.runtime.exporter.stdout.internal.config.StdoutExporterConfig;
+
+public class StdoutExporterContext implements ExporterContext
+{
+ private final StdoutConfiguration config;
+ private final EngineContext context;
+
+ public StdoutExporterContext(
+ StdoutConfiguration config,
+ EngineContext context)
+ {
+ this.config = config;
+ this.context = context;
+ }
+
+ @Override
+ public ExporterHandler attach(
+ ExporterConfig exporter,
+ List attributes,
+ Collector collector,
+ LongFunction resolveKind)
+ {
+ StdoutExporterConfig stdoutExporter = new StdoutExporterConfig(exporter);
+ return new StdoutExporterHandler(config, context, stdoutExporter);
+ }
+
+ @Override
+ public void detach(
+ long exporterId)
+ {
+ }
+
+ public String supplyQName(
+ long namespacedId)
+ {
+ return context.supplyQName(namespacedId);
+ }
+
+ public int supplyTypeId(
+ String name)
+ {
+ return context.supplyTypeId(name);
+ }
+
+ public MessageReader supplyEventReader()
+ {
+ return context.supplyEventReader();
+ }
+}
diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactorySpi.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactorySpi.java
new file mode 100644
index 0000000000..a22c55f81f
--- /dev/null
+++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactorySpi.java
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal;
+
+import io.aklivity.zilla.runtime.engine.Configuration;
+import io.aklivity.zilla.runtime.engine.exporter.Exporter;
+import io.aklivity.zilla.runtime.engine.exporter.ExporterFactorySpi;
+
+public class StdoutExporterFactorySpi implements ExporterFactorySpi
+{
+ @Override
+ public String type()
+ {
+ return StdoutExporter.NAME;
+ }
+
+ @Override
+ public Exporter create(
+ Configuration config)
+ {
+ return new StdoutExporter(new StdoutConfiguration(config));
+ }
+}
diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterHandler.java
new file mode 100644
index 0000000000..98a0ed322e
--- /dev/null
+++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterHandler.java
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal;
+
+import java.io.PrintStream;
+
+import io.aklivity.zilla.runtime.engine.EngineContext;
+import io.aklivity.zilla.runtime.engine.exporter.ExporterHandler;
+import io.aklivity.zilla.runtime.exporter.stdout.internal.config.StdoutExporterConfig;
+import io.aklivity.zilla.runtime.exporter.stdout.internal.stream.StdoutEventsStream;
+
+public class StdoutExporterHandler implements ExporterHandler
+{
+ private final StdoutExporterContext context;
+ private final PrintStream out;
+
+ private StdoutEventsStream events;
+
+ public StdoutExporterHandler(
+ StdoutConfiguration config,
+ EngineContext context,
+ StdoutExporterConfig exporter)
+ {
+ this.context = new StdoutExporterContext(config, context);
+ this.out = config.output();
+ }
+
+ @Override
+ public void start()
+ {
+ events = new StdoutEventsStream(context, out);
+ }
+
+ @Override
+ public int export()
+ {
+ return events.process();
+ }
+
+ @Override
+ public void stop()
+ {
+ this.events = null;
+ }
+}
diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutExporterConfig.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutExporterConfig.java
new file mode 100644
index 0000000000..8f9f7ca38a
--- /dev/null
+++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutExporterConfig.java
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal.config;
+
+import io.aklivity.zilla.runtime.engine.config.ExporterConfig;
+
+public class StdoutExporterConfig
+{
+ private final StdoutOptionsConfig options;
+
+ public StdoutExporterConfig(
+ ExporterConfig exporter)
+ {
+ this.options = (StdoutOptionsConfig)exporter.options;
+ }
+
+ public StdoutOptionsConfig options()
+ {
+ return options;
+ }
+}
diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfig.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfig.java
new file mode 100644
index 0000000000..43f11bb071
--- /dev/null
+++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfig.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal.config;
+
+import io.aklivity.zilla.runtime.engine.config.OptionsConfig;
+
+public class StdoutOptionsConfig extends OptionsConfig
+{
+}
diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapter.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapter.java
new file mode 100644
index 0000000000..fb2bbf8068
--- /dev/null
+++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapter.java
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal.config;
+
+import static io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi.Kind.EXPORTER;
+
+import jakarta.json.Json;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonObjectBuilder;
+import jakarta.json.bind.adapter.JsonbAdapter;
+
+import io.aklivity.zilla.runtime.engine.config.OptionsConfig;
+import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi;
+import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporter;
+
+public class StdoutOptionsConfigAdapter implements OptionsConfigAdapterSpi, JsonbAdapter
+{
+ @Override
+ public Kind kind()
+ {
+ return EXPORTER;
+ }
+
+ @Override
+ public String type()
+ {
+ return StdoutExporter.NAME;
+ }
+
+ @Override
+ public JsonObject adaptToJson(
+ OptionsConfig options)
+ {
+ JsonObjectBuilder object = Json.createObjectBuilder();
+ return object.build();
+ }
+
+ @Override
+ public OptionsConfig adaptFromJson(
+ JsonObject object)
+ {
+ return new StdoutOptionsConfig();
+ }
+}
diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/EventHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/EventHandler.java
new file mode 100644
index 0000000000..f8f2a84e8b
--- /dev/null
+++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/EventHandler.java
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal.stream;
+
+import java.io.PrintStream;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+
+import org.agrona.DirectBuffer;
+
+import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext;
+import io.aklivity.zilla.runtime.exporter.stdout.internal.types.StringFW;
+
+public abstract class EventHandler
+{
+ protected static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("dd/MMM/yyyy:HH:mm:ss Z");
+
+ protected final StdoutExporterContext context;
+ protected final PrintStream out;
+
+ public EventHandler(
+ StdoutExporterContext context,
+ PrintStream out)
+ {
+ this.context = context;
+ this.out = out;
+ }
+
+ public abstract void handleEvent(
+ int msgTypeId,
+ DirectBuffer buffer,
+ int index,
+ int length);
+
+ protected static String asString(
+ StringFW stringFW)
+ {
+ String s = stringFW.asString();
+ return s == null ? "" : s;
+ }
+
+ protected static String identity(
+ StringFW identity)
+ {
+ int length = identity.length();
+ return length <= 0 ? "-" : identity.asString();
+ }
+
+ protected static String asDateTime(
+ long timestamp)
+ {
+ Instant instant = Instant.ofEpochMilli(timestamp);
+ OffsetDateTime offsetDateTime = OffsetDateTime.ofInstant(instant, ZoneId.systemDefault());
+ return offsetDateTime.format(FORMATTER);
+ }
+}
diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutEventsStream.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutEventsStream.java
new file mode 100644
index 0000000000..c6e330f377
--- /dev/null
+++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutEventsStream.java
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal.stream;
+
+import java.io.PrintStream;
+
+import org.agrona.DirectBuffer;
+import org.agrona.collections.Int2ObjectHashMap;
+
+import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer;
+import io.aklivity.zilla.runtime.engine.binding.function.MessageReader;
+import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext;
+
+public class StdoutEventsStream
+{
+ private final MessageReader readEvent;
+ private final Int2ObjectHashMap eventHandlers;
+
+ public StdoutEventsStream(
+ StdoutExporterContext context,
+ PrintStream out)
+ {
+ this.readEvent = context.supplyEventReader();
+
+ final Int2ObjectHashMap eventHandlers = new Int2ObjectHashMap<>();
+ eventHandlers.put(context.supplyTypeId("http"), new StdoutHttpHandler(context, out)::handleEvent);
+ eventHandlers.put(context.supplyTypeId("jwt"), new StdoutJwtHandler(context, out)::handleEvent);
+ eventHandlers.put(context.supplyTypeId("kafka"), new StdoutKafkaHandler(context, out)::handleEvent);
+ eventHandlers.put(context.supplyTypeId("schema-registry"),
+ new StdoutSchemaRegistryHandler(context, out)::handleEvent);
+ eventHandlers.put(context.supplyTypeId("tcp"), new StdoutTcpHandler(context, out)::handleEvent);
+ eventHandlers.put(context.supplyTypeId("tls"), new StdoutTlsHandler(context, out)::handleEvent);
+ this.eventHandlers = eventHandlers;
+ }
+
+ public int process()
+ {
+ return readEvent.read(this::handleEvent, 1);
+ }
+
+ private void handleEvent(
+ int msgTypeId,
+ DirectBuffer buffer,
+ int index,
+ int length)
+ {
+ final MessageConsumer handler = eventHandlers.get(msgTypeId);
+ if (handler != null)
+ {
+ handler.accept(msgTypeId, buffer, index, length);
+ }
+ }
+}
diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutHttpHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutHttpHandler.java
new file mode 100644
index 0000000000..86dabe799b
--- /dev/null
+++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutHttpHandler.java
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal.stream;
+
+import java.io.PrintStream;
+
+import org.agrona.DirectBuffer;
+
+import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext;
+import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.HttpEventFW;
+import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.HttpRequestAcceptedFW;
+
+public class StdoutHttpHandler extends EventHandler
+{
+ private static final String REQUEST_ACCEPTED_FORMAT = "%s %s [%s] REQUEST_ACCEPTED %s %s %s %s%n";
+
+ private final HttpEventFW httpEventRO = new HttpEventFW();
+
+ public StdoutHttpHandler(
+ StdoutExporterContext context,
+ PrintStream out)
+ {
+ super(context, out);
+ }
+
+ public void handleEvent(
+ int msgTypeId,
+ DirectBuffer buffer,
+ int index,
+ int length)
+ {
+ final HttpEventFW event = httpEventRO.wrap(buffer, index, index + length);
+ switch (event.kind())
+ {
+ case REQUEST_ACCEPTED:
+ {
+ HttpRequestAcceptedFW e = event.requestAccepted();
+ String qname = context.supplyQName(e.namespacedId());
+ out.format(REQUEST_ACCEPTED_FORMAT, qname, identity(e.identity()), asDateTime(e.timestamp()), asString(e.scheme()),
+ asString(e.method()), asString(e.authority()), asString(e.path()));
+ break;
+ }
+ }
+ }
+}
diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutJwtHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutJwtHandler.java
new file mode 100644
index 0000000000..7acf1ee0fe
--- /dev/null
+++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutJwtHandler.java
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal.stream;
+
+import java.io.PrintStream;
+
+import org.agrona.DirectBuffer;
+
+import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext;
+import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.JwtAuthorizationFailedFW;
+import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.JwtEventFW;
+
+public class StdoutJwtHandler extends EventHandler
+{
+ private static final String AUTHORIZATION_FAILED_FORMAT = "%s %s [%s] AUTHORIZATION_FAILED%n";
+
+ private final JwtEventFW jwtEventRO = new JwtEventFW();
+
+ public StdoutJwtHandler(
+ StdoutExporterContext context,
+ PrintStream out)
+ {
+ super(context, out);
+ }
+
+ public void handleEvent(
+ int msgTypeId,
+ DirectBuffer buffer,
+ int index,
+ int length)
+ {
+ JwtEventFW event = jwtEventRO.wrap(buffer, index, index + length);
+ switch (event.kind())
+ {
+ case AUTHORIZATION_FAILED:
+ JwtAuthorizationFailedFW e = event.authorizationFailed();
+ String qname = context.supplyQName(e.namespacedId());
+ out.printf(AUTHORIZATION_FAILED_FORMAT, qname, identity(e.identity()), asDateTime(e.timestamp()));
+ break;
+ }
+ }
+}
diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutKafkaHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutKafkaHandler.java
new file mode 100644
index 0000000000..bae2026051
--- /dev/null
+++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutKafkaHandler.java
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal.stream;
+
+import java.io.PrintStream;
+
+import org.agrona.DirectBuffer;
+
+import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext;
+import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.EventFW;
+import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.KafkaApiVersionRejectedFW;
+import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.KafkaEventFW;
+
+public class StdoutKafkaHandler extends EventHandler
+{
+ private static final String AUTHORIZATION_FAILED_FORMAT = "%s - [%s] AUTHORIZATION_FAILED%n";
+ private static final String API_VERSION_REJECTED_FORMAT = "%s - [%s] API_VERSION_REJECTED %d %d%n";
+
+ private final KafkaEventFW kafkaEventRO = new KafkaEventFW();
+
+ public StdoutKafkaHandler(
+ StdoutExporterContext context,
+ PrintStream out)
+ {
+ super(context, out);
+ }
+
+ public void handleEvent(
+ int msgTypeId,
+ DirectBuffer buffer,
+ int index,
+ int length)
+ {
+ final KafkaEventFW event = kafkaEventRO.wrap(buffer, index, index + length);
+ switch (event.kind())
+ {
+ case AUTHORIZATION_FAILED:
+ {
+ EventFW e = event.authorizationFailed();
+ String qname = context.supplyQName(e.namespacedId());
+ out.printf(AUTHORIZATION_FAILED_FORMAT, qname, asDateTime(e.timestamp()));
+ break;
+ }
+ case API_VERSION_REJECTED:
+ {
+ KafkaApiVersionRejectedFW e = event.apiVersionRejected();
+ String qname = context.supplyQName(e.namespacedId());
+ out.printf(API_VERSION_REJECTED_FORMAT, qname, asDateTime(e.timestamp()), e.apiKey(), e.apiVersion());
+ break;
+ }
+ }
+ }
+}
diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutSchemaRegistryHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutSchemaRegistryHandler.java
new file mode 100644
index 0000000000..376826b0fd
--- /dev/null
+++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutSchemaRegistryHandler.java
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal.stream;
+
+import java.io.PrintStream;
+
+import org.agrona.DirectBuffer;
+
+import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext;
+import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.SchemaRegistryEventFW;
+import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.SchemaRegistryRemoteAccessRejectedFW;
+
+public class StdoutSchemaRegistryHandler extends EventHandler
+{
+ private static final String REMOTE_ACCESS_REJECTED = "%s - [%s] REMOTE_ACCESS_REJECTED %s %s %d%n";
+
+ private final SchemaRegistryEventFW schemaRegistryEventRO = new SchemaRegistryEventFW();
+
+ public StdoutSchemaRegistryHandler(
+ StdoutExporterContext context,
+ PrintStream out)
+ {
+ super(context, out);
+ }
+
+ public void handleEvent(
+ int msgTypeId,
+ DirectBuffer buffer,
+ int index,
+ int length)
+ {
+ SchemaRegistryEventFW event = schemaRegistryEventRO.wrap(buffer, index, index + length);
+ switch (event.kind())
+ {
+ case REMOTE_ACCESS_REJECTED:
+ SchemaRegistryRemoteAccessRejectedFW e = event.remoteAccessRejected();
+ String qname = context.supplyQName(e.namespacedId());
+ out.printf(REMOTE_ACCESS_REJECTED, qname, asDateTime(e.timestamp()), asString(e.method()), asString(e.url()),
+ e.status());
+ break;
+ }
+ }
+}
diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTcpHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTcpHandler.java
new file mode 100644
index 0000000000..0584a4eddf
--- /dev/null
+++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTcpHandler.java
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal.stream;
+
+import java.io.PrintStream;
+
+import org.agrona.DirectBuffer;
+
+import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext;
+import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.TcpDnsFailedFW;
+import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.TcpEventFW;
+
+public class StdoutTcpHandler extends EventHandler
+{
+ private static final String DNS_FAILED_FORMAT = "%s - [%s] DNS_FAILED %s%n";
+
+ private final TcpEventFW tcpEventRO = new TcpEventFW();
+
+ public StdoutTcpHandler(
+ StdoutExporterContext context,
+ PrintStream out)
+ {
+ super(context, out);
+ }
+
+ public void handleEvent(
+ int msgTypeId,
+ DirectBuffer buffer,
+ int index,
+ int length)
+ {
+ final TcpEventFW event = tcpEventRO.wrap(buffer, index, index + length);
+ switch (event.kind())
+ {
+ case DNS_FAILED:
+ TcpDnsFailedFW e = event.dnsFailed();
+ String qname = context.supplyQName(e.namespacedId());
+ out.printf(DNS_FAILED_FORMAT, qname, asDateTime(e.timestamp()), asString(e.address()));
+ break;
+ }
+ }
+}
diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTlsHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTlsHandler.java
new file mode 100644
index 0000000000..a3ecce6abf
--- /dev/null
+++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTlsHandler.java
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal.stream;
+
+import java.io.PrintStream;
+
+import org.agrona.DirectBuffer;
+
+import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext;
+import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.EventFW;
+import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.TlsEventFW;
+
+public class StdoutTlsHandler extends EventHandler
+{
+ private static final String TLS_FAILED_FORMAT = "%s - [%s] TLS_FAILED%n";
+ private static final String PROTOCOL_REJECTED_FORMAT = "%s - [%s] PROTOCOL_REJECTED%n";
+ private static final String KEY_REJECTED_FORMAT = "%s - [%s] KEY_REJECTED%n";
+ private static final String PEER_NOT_VERIFIED_FORMAT = "%s - [%s] PEER_NOT_VERIFIED%n";
+ private static final String HANDSHAKE_FAILED_FORMAT = "%s - [%s] HANDSHAKE_FAILED%n";
+
+ private final TlsEventFW tlsEventRO = new TlsEventFW();
+
+ public StdoutTlsHandler(
+ StdoutExporterContext context,
+ PrintStream out)
+ {
+ super(context, out);
+ }
+
+ public void handleEvent(
+ int msgTypeId,
+ DirectBuffer buffer,
+ int index,
+ int length)
+ {
+ TlsEventFW event = tlsEventRO.wrap(buffer, index, index + length);
+ switch (event.kind())
+ {
+ case TLS_FAILED:
+ {
+ EventFW e = event.tlsFailed();
+ String qname = context.supplyQName(e.namespacedId());
+ out.printf(TLS_FAILED_FORMAT, qname, asDateTime(e.timestamp()));
+ break;
+ }
+ case TLS_PROTOCOL_REJECTED:
+ {
+ EventFW e = event.tlsProtocolRejected();
+ String qname = context.supplyQName(e.namespacedId());
+ out.printf(PROTOCOL_REJECTED_FORMAT, qname, asDateTime(e.timestamp()));
+ break;
+ }
+ case TLS_KEY_REJECTED:
+ {
+ EventFW e = event.tlsKeyRejected();
+ String qname = context.supplyQName(e.namespacedId());
+ out.printf(KEY_REJECTED_FORMAT, qname, asDateTime(e.timestamp()));
+ break;
+ }
+ case TLS_PEER_NOT_VERIFIED:
+ {
+ EventFW e = event.tlsPeerNotVerified();
+ String qname = context.supplyQName(e.namespacedId());
+ out.printf(PEER_NOT_VERIFIED_FORMAT, qname, asDateTime(e.timestamp()));
+ break;
+ }
+ case TLS_HANDSHAKE_FAILED:
+ {
+ EventFW e = event.tlsHandshakeFailed();
+ String qname = context.supplyQName(e.namespacedId());
+ out.printf(HANDSHAKE_FAILED_FORMAT, qname, asDateTime(e.timestamp()));
+ break;
+ }
+ }
+ }
+}
diff --git a/incubator/exporter-stdout/src/main/moditect/module-info.java b/incubator/exporter-stdout/src/main/moditect/module-info.java
new file mode 100644
index 0000000000..c89775c1c8
--- /dev/null
+++ b/incubator/exporter-stdout/src/main/moditect/module-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+module io.aklivity.zilla.runtime.exporter.stdout
+{
+ requires io.aklivity.zilla.runtime.engine;
+
+ provides io.aklivity.zilla.runtime.engine.exporter.ExporterFactorySpi
+ with io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterFactorySpi;
+
+ provides io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi
+ with io.aklivity.zilla.runtime.exporter.stdout.internal.config.StdoutOptionsConfigAdapter;
+}
diff --git a/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi b/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi
new file mode 100644
index 0000000000..c0bfc37413
--- /dev/null
+++ b/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi
@@ -0,0 +1 @@
+io.aklivity.zilla.runtime.exporter.stdout.internal.config.StdoutOptionsConfigAdapter
\ No newline at end of file
diff --git a/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.exporter.ExporterFactorySpi b/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.exporter.ExporterFactorySpi
new file mode 100644
index 0000000000..8f575d444b
--- /dev/null
+++ b/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.exporter.ExporterFactorySpi
@@ -0,0 +1 @@
+io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterFactorySpi
\ No newline at end of file
diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactoryTest.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactoryTest.java
new file mode 100644
index 0000000000..3389e46b3a
--- /dev/null
+++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactoryTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+import java.net.URL;
+
+import org.junit.Test;
+
+import io.aklivity.zilla.runtime.engine.Configuration;
+import io.aklivity.zilla.runtime.engine.EngineContext;
+import io.aklivity.zilla.runtime.engine.exporter.Exporter;
+import io.aklivity.zilla.runtime.engine.exporter.ExporterContext;
+import io.aklivity.zilla.runtime.engine.exporter.ExporterFactory;
+
+public final class StdoutExporterFactoryTest
+{
+ @Test
+ public void shouldLoadAndCreate()
+ {
+ // GIVEN
+ Configuration config = new Configuration();
+ ExporterFactory factory = ExporterFactory.instantiate();
+
+ // WHEN
+ Exporter exporter = factory.create("stdout", config);
+ ExporterContext context = exporter.supply(mock(EngineContext.class));
+
+ // THEN
+ assertThat(exporter, instanceOf(StdoutExporter.class));
+ assertThat(exporter.name(), equalTo("stdout"));
+ assertThat(exporter.type(), instanceOf(URL.class));
+ assertThat(context, instanceOf(ExporterContext.class));
+ }
+}
diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapterTest.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapterTest.java
new file mode 100644
index 0000000000..a0fdab3dbe
--- /dev/null
+++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapterTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal.config;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.nullValue;
+
+import jakarta.json.bind.Jsonb;
+import jakarta.json.bind.JsonbBuilder;
+import jakarta.json.bind.JsonbConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class StdoutOptionsConfigAdapterTest
+{
+ private Jsonb jsonb;
+
+ @Before
+ public void initJson()
+ {
+ JsonbConfig config = new JsonbConfig()
+ .withAdapters(new StdoutOptionsConfigAdapter());
+ jsonb = JsonbBuilder.create(config);
+ }
+
+ @Test
+ public void shouldReadOptions()
+ {
+ // GIVEN
+ String text = "{}";
+
+ // WHEN
+ StdoutOptionsConfig options = jsonb.fromJson(text, StdoutOptionsConfig.class);
+
+ // THEN
+ assertThat(options, not(nullValue()));
+ }
+
+ @Test
+ public void shouldWriteOptions()
+ {
+ // GIVEN
+ String expectedText = "{}";
+ StdoutOptionsConfig config = new StdoutOptionsConfig();
+
+ // WHEN
+ String text = jsonb.toJson(config);
+
+ // THEN
+ assertThat(text, not(nullValue()));
+ assertThat(text, equalTo(expectedText));
+ }
+}
diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http11EventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http11EventsIT.java
new file mode 100644
index 0000000000..c942d6e141
--- /dev/null
+++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http11EventsIT.java
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal.events;
+
+import static io.aklivity.zilla.runtime.engine.EngineConfiguration.ENGINE_BUFFER_SLOT_CAPACITY;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.rules.RuleChain.outerRule;
+
+import java.util.regex.Pattern;
+
+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;
+import io.aklivity.zilla.runtime.engine.test.annotation.Configure;
+
+public class Http11EventsIT
+{
+ private final K3poRule k3po = new K3poRule()
+ .addScriptRoot("net", "io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/message.format")
+ .addScriptRoot("app", "io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/message.format");
+
+ private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS));
+
+ private final EngineRule engine = new EngineRule()
+ .directory("target/zilla-itests")
+ .countersBufferCapacity(8192)
+ .configure(ENGINE_BUFFER_SLOT_CAPACITY, 8192)
+ .configurationRoot("io/aklivity/zilla/specs/binding/http/config/v1.1")
+ .external("app0")
+ .clean();
+
+ private final StdoutOutputRule output = new StdoutOutputRule();
+
+ @Rule
+ public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout);
+
+ @Test
+ @Configuration("server.yaml")
+ @Specification({
+ "${net}/request.with.headers/client",
+ "${app}/request.with.headers/server" })
+ @Configure(name = "zilla.exporter.stdout.output",
+ value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT")
+ public void requestWithHeaders() throws Exception
+ {
+ k3po.finish();
+ output.expect(Pattern.compile("test.app0 - \\[[^\\]]+\\] REQUEST_ACCEPTED http GET localhost:8080 /\n"));
+ }
+}
diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http2EventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http2EventsIT.java
new file mode 100644
index 0000000000..e858786c85
--- /dev/null
+++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http2EventsIT.java
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal.events;
+
+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 java.util.regex.Pattern;
+
+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;
+import io.aklivity.zilla.runtime.engine.test.annotation.Configure;
+
+public class Http2EventsIT
+{
+ private final K3poRule k3po = new K3poRule()
+ .addScriptRoot("net", "io/aklivity/zilla/specs/binding/http/streams/network/rfc7540/message.format")
+ .addScriptRoot("app", "io/aklivity/zilla/specs/binding/http/streams/application/rfc7540/message.format");
+
+ private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS));
+
+ private final EngineRule engine = new EngineRule()
+ .directory("target/zilla-itests")
+ .countersBufferCapacity(8192)
+ .configure(HTTP_CONCURRENT_STREAMS, 100)
+ .configurationRoot("io/aklivity/zilla/specs/binding/http/config/v2")
+ .external("app0")
+ .clean();
+
+ private final StdoutOutputRule output = new StdoutOutputRule();
+
+ @Rule
+ public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout);
+
+ @Test
+ @Configuration("server.yaml")
+ @Specification({
+ "${net}/connection.headers/client",
+ "${app}/connection.headers/server" })
+ @Configure(name = "zilla.exporter.stdout.output",
+ value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT")
+ public void connectionHeaders() throws Exception
+ {
+ k3po.finish();
+ output.expect(Pattern.compile("test.net0 - \\[[^\\]]+\\] REQUEST_ACCEPTED http GET localhost:8080 /\n"));
+ }
+}
diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/JwtEventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/JwtEventsIT.java
new file mode 100644
index 0000000000..41fa36d119
--- /dev/null
+++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/JwtEventsIT.java
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal.events;
+
+import static io.aklivity.zilla.runtime.binding.http.internal.HttpConfiguration.HTTP_SERVER_HEADER;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.rules.RuleChain.outerRule;
+
+import java.util.regex.Pattern;
+
+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;
+import io.aklivity.zilla.runtime.engine.test.annotation.Configure;
+
+public class JwtEventsIT
+{
+ private final K3poRule k3po = new K3poRule()
+ .addScriptRoot("net", "io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization")
+ .addScriptRoot("app", "io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/authorization");
+
+ private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS));
+
+ private final EngineRule engine = new EngineRule()
+ .directory("target/zilla-itests")
+ .countersBufferCapacity(8192)
+ .configure(HTTP_SERVER_HEADER, "Zilla")
+ .configurationRoot("io/aklivity/zilla/specs/binding/http/config/v1.1")
+ .external("app0")
+ .clean();
+
+ private final StdoutOutputRule output = new StdoutOutputRule();
+
+ @Rule
+ public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout);
+
+ @Test
+ @Configuration("server.authorization.credentials.yaml")
+ @Specification({
+ "${net}/reject.credentials.header/client",
+ })
+ @Configure(name = "zilla.exporter.stdout.output",
+ value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT")
+ public void shouldRejectCredentialsHeader() throws Exception
+ {
+ k3po.finish();
+ output.expect(Pattern.compile("test.net0 user \\[[^\\]]+\\] AUTHORIZATION_FAILED\n"));
+ }
+}
diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/KafkaEventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/KafkaEventsIT.java
new file mode 100644
index 0000000000..e1e8c9fda7
--- /dev/null
+++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/KafkaEventsIT.java
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal.events;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.rules.RuleChain.outerRule;
+
+import java.util.regex.Pattern;
+
+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;
+import io.aklivity.zilla.runtime.engine.test.annotation.Configure;
+
+public class KafkaEventsIT
+{
+ private final K3poRule k3po = new K3poRule()
+ .addScriptRoot("net", "io/aklivity/zilla/specs/binding/kafka/streams/network/group.f1.j5.s3.l3.h3")
+ .addScriptRoot("app", "io/aklivity/zilla/specs/binding/kafka/streams/application/group");
+
+ private final TestRule timeout = new DisableOnDebug(new Timeout(15, SECONDS));
+
+ private final EngineRule engine = new EngineRule()
+ .directory("target/zilla-itests")
+ .countersBufferCapacity(8192)
+ .configure("zilla.binding.kafka.client.instance.id",
+ "io.aklivity.zilla.runtime.exporter.stdout.internal.events.KafkaEventsIT::supplyInstanceId")
+ .configurationRoot("io/aklivity/zilla/specs/binding/kafka/config")
+ .external("net0")
+ .clean();
+
+ private final StdoutOutputRule output = new StdoutOutputRule();
+
+ @Rule
+ public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout);
+
+ @Test
+ @Configuration("client.yaml")
+ @Specification({
+ "${app}/invalid.describe.config/client",
+ "${net}/invalid.describe.config/server"})
+ @Configure(name = "zilla.exporter.stdout.output",
+ value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT")
+ public void shouldHandleInvalidDescribeConfig() throws Exception
+ {
+ k3po.finish();
+ output.expect(Pattern.compile("test.app0 - \\[[^\\]]+\\] API_VERSION_REJECTED 32 0\n"));
+ }
+
+ public static String supplyInstanceId()
+ {
+ return "zilla";
+ }
+}
diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/StdoutOutputRule.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/StdoutOutputRule.java
new file mode 100644
index 0000000000..be79fd3ba8
--- /dev/null
+++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/StdoutOutputRule.java
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal.events;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.matchesPattern;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.util.regex.Pattern;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+public final class StdoutOutputRule implements TestRule
+{
+ public static final PrintStream OUT;
+
+ private static final ByteArrayOutputStream BOS;
+
+ static
+ {
+ BOS = new ByteArrayOutputStream();
+ OUT = new PrintStream(BOS);
+ }
+
+ private Pattern expected;
+
+ @Override
+ public Statement apply(
+ Statement base,
+ Description description)
+ {
+ return new Statement()
+ {
+ @Override
+ public void evaluate() throws Throwable
+ {
+ BOS.reset();
+ base.evaluate();
+ assertThat(BOS.toString(StandardCharsets.UTF_8), matchesPattern(expected));
+ }
+ };
+ }
+
+ public void expect(
+ Pattern expected)
+ {
+ this.expected = expected;
+ }
+}
diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TcpEventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TcpEventsIT.java
new file mode 100644
index 0000000000..989f89eb83
--- /dev/null
+++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TcpEventsIT.java
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal.events;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.rules.RuleChain.outerRule;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.regex.Pattern;
+
+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;
+import io.aklivity.zilla.runtime.engine.test.annotation.Configure;
+
+public class TcpEventsIT
+{
+ private final K3poRule k3po = new K3poRule()
+ .addScriptRoot("net", "io/aklivity/zilla/specs/binding/tcp/streams/network/rfc793")
+ .addScriptRoot("app", "io/aklivity/zilla/specs/binding/tcp/streams/application/rfc793");
+
+ private final TestRule timeout = new DisableOnDebug(new Timeout(5, SECONDS));
+
+ private final EngineRule engine = new EngineRule()
+ .directory("target/zilla-itests")
+ .countersBufferCapacity(4096)
+ .configurationRoot("io/aklivity/zilla/specs/binding/tcp/config")
+ .clean();
+
+ private final StdoutOutputRule output = new StdoutOutputRule();
+
+ @Rule
+ public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout);
+
+ @Test
+ @Configuration("client.host.yaml")
+ @Specification({
+ "${app}/connection.failed/client"
+ })
+ @Configure(name = "zilla.exporter.stdout.output",
+ value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT")
+ @Configure(name = "zilla.engine.host.resolver",
+ value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.TcpEventsIT::resolveHost")
+ public void dnsResolutionFailed() throws Exception
+ {
+ k3po.finish();
+ output.expect(Pattern.compile("test.app0 - \\[[^\\]]+\\] DNS_FAILED localhost\n"));
+ }
+
+ public static InetAddress[] resolveHost(
+ String host) throws UnknownHostException
+ {
+ throw new UnknownHostException();
+ }
+}
diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TlsEventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TlsEventsIT.java
new file mode 100644
index 0000000000..cb201e110c
--- /dev/null
+++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TlsEventsIT.java
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.exporter.stdout.internal.events;
+
+import static io.aklivity.zilla.runtime.engine.EngineConfiguration.ENGINE_DRAIN_ON_CLOSE;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.rules.RuleChain.outerRule;
+
+import java.util.regex.Pattern;
+
+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;
+import io.aklivity.zilla.runtime.engine.test.annotation.Configure;
+
+public class TlsEventsIT
+{
+ private final K3poRule k3po = new K3poRule()
+ .addScriptRoot("net", "io/aklivity/zilla/specs/binding/tls/streams/network")
+ .addScriptRoot("app", "io/aklivity/zilla/specs/binding/tls/streams/application");
+
+ 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/tls/config")
+ .external("app0")
+ .configure(ENGINE_DRAIN_ON_CLOSE, false)
+ .clean();
+
+ private final StdoutOutputRule output = new StdoutOutputRule();
+
+ @Rule
+ public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout);
+
+ @Test
+ @Configuration("server.yaml")
+ @Specification({
+ "${net}/client.hello.malformed/client"})
+ @Configure(name = "zilla.exporter.stdout.output",
+ value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT")
+ public void shouldResetMalformedClientHello() throws Exception
+ {
+ k3po.finish();
+ output.expect(Pattern.compile("test.net0 - \\[[^\\]]+\\] TLS_FAILED\n"));
+ }
+
+ @Test
+ @Configuration("server.yaml")
+ @Specification({
+ "${net}/server.handshake.timeout/client"})
+ @Configure(name = "zilla.binding.tls.handshake.timeout", value = "1")
+ @Configure(name = "zilla.exporter.stdout.output",
+ value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT")
+ public void shouldTimeoutHandshake() throws Exception
+ {
+ k3po.finish();
+ output.expect(Pattern.compile("test.net0 - \\[[^\\]]+\\] HANDSHAKE_FAILED\n"));
+ }
+}
diff --git a/incubator/pom.xml b/incubator/pom.xml
index faa3f73b59..7f57daef52 100644
--- a/incubator/pom.xml
+++ b/incubator/pom.xml
@@ -21,6 +21,7 @@
catalog-inline.spec
catalog-schema-registry.spec
exporter-otlp.spec
+ exporter-stdout.spec
model-avro.spec
model-core.spec
model-json.spec
@@ -37,6 +38,7 @@
command-tune
exporter-otlp
+ exporter-stdout
model-avro
model-core
@@ -86,6 +88,11 @@
exporter-otlp
${project.version}
+
+ ${project.groupId}
+ exporter-stdout
+ ${project.version}
+
${project.groupId}
model-avro
diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/HttpEventContext.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/HttpEventContext.java
new file mode 100644
index 0000000000..f4c0ee0fc1
--- /dev/null
+++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/HttpEventContext.java
@@ -0,0 +1,102 @@
+/*
+ * 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;
+
+import java.nio.ByteBuffer;
+import java.time.Clock;
+import java.util.Map;
+
+import org.agrona.concurrent.AtomicBuffer;
+import org.agrona.concurrent.UnsafeBuffer;
+
+import io.aklivity.zilla.runtime.binding.http.internal.types.Array32FW;
+import io.aklivity.zilla.runtime.binding.http.internal.types.HttpHeaderFW;
+import io.aklivity.zilla.runtime.binding.http.internal.types.String8FW;
+import io.aklivity.zilla.runtime.binding.http.internal.types.event.HttpEventFW;
+import io.aklivity.zilla.runtime.engine.EngineContext;
+import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer;
+import io.aklivity.zilla.runtime.engine.guard.GuardHandler;
+
+public class HttpEventContext
+{
+ private static final int EVENT_BUFFER_CAPACITY = 2048;
+ private static final String8FW HEADER_SCHEME = new String8FW(":scheme");
+ private static final String8FW HEADER_METHOD = new String8FW(":method");
+ private static final String8FW HEADER_AUTHORITY = new String8FW(":authority");
+ private static final String8FW HEADER_PATH = new String8FW(":path");
+
+ private final AtomicBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY));
+ private final HttpEventFW.Builder httpEventRW = new HttpEventFW.Builder();
+ private final int httpTypeId;
+ private final MessageConsumer eventWriter;
+ private final Clock clock;
+
+ public HttpEventContext(
+ EngineContext context)
+ {
+ this.httpTypeId = context.supplyTypeId(HttpBinding.NAME);
+ this.eventWriter = context.supplyEventWriter();
+ this.clock = context.clock();
+ }
+
+ public void requestAccepted(
+ long traceId,
+ long bindingId,
+ GuardHandler guard,
+ long authorization,
+ Map headers)
+ {
+ String identity = guard == null ? null : guard.identity(authorization);
+ HttpEventFW event = httpEventRW
+ .wrap(eventBuffer, 0, eventBuffer.capacity())
+ .requestAccepted(e -> e
+ .timestamp(clock.millis())
+ .traceId(traceId)
+ .namespacedId(bindingId)
+ .identity(identity)
+ .scheme(headers.get(":scheme"))
+ .method(headers.get(":method"))
+ .authority(headers.get(":authority"))
+ .path(headers.get(":path"))
+ )
+ .build();
+ eventWriter.accept(httpTypeId, event.buffer(), event.offset(), event.limit());
+ }
+
+ public void requestAccepted(
+ long traceId,
+ long bindingId,
+ GuardHandler guard,
+ long authorization,
+ Array32FW headers)
+ {
+ String identity = guard == null ? null : guard.identity(authorization);
+ HttpEventFW event = httpEventRW
+ .wrap(eventBuffer, 0, eventBuffer.capacity())
+ .requestAccepted(e -> e
+ .timestamp(clock.millis())
+ .traceId(traceId)
+ .namespacedId(bindingId)
+ .identity(identity)
+ .scheme(headers.matchFirst(h -> HEADER_SCHEME.equals(h.name())).value().asString())
+ .method(headers.matchFirst(h -> HEADER_METHOD.equals(h.name())).value().asString())
+ .authority(headers.matchFirst(h -> HEADER_AUTHORITY.equals(h.name())).value().asString())
+ .path(headers.matchFirst(h -> HEADER_PATH.equals(h.name())).value().asString())
+ )
+ .build();
+ eventWriter.accept(httpTypeId, event.buffer(), event.offset(), event.limit());
+ }
+}
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 39b9bea739..e8f99951e9 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
@@ -84,6 +84,7 @@
import io.aklivity.zilla.runtime.binding.http.config.HttpVersion;
import io.aklivity.zilla.runtime.binding.http.internal.HttpBinding;
import io.aklivity.zilla.runtime.binding.http.internal.HttpConfiguration;
+import io.aklivity.zilla.runtime.binding.http.internal.HttpEventContext;
import io.aklivity.zilla.runtime.binding.http.internal.codec.Http2ContinuationFW;
import io.aklivity.zilla.runtime.binding.http.internal.codec.Http2DataFW;
import io.aklivity.zilla.runtime.binding.http.internal.codec.Http2ErrorCode;
@@ -544,6 +545,7 @@ public final class HttpServerFactory implements HttpStreamFactory
private final Matcher connectionClose;
private final int maximumHeadersSize;
private final Long2ObjectHashMap bindings;
+ private final HttpEventContext event;
public HttpServerFactory(
HttpConfiguration config,
@@ -576,6 +578,7 @@ public HttpServerFactory(
this.supplyValidator = context::supplyValidator;
this.encodeMax = bufferPool.slotCapacity();
this.bindings = new Long2ObjectHashMap<>();
+ this.event = new HttpEventContext(context);
this.headers200 = initHeaders(config, STATUS_200);
this.headers204 = initHeaders(config, STATUS_204);
@@ -1043,7 +1046,7 @@ else if (!isCorsRequestAllowed(server.binding, headers))
final String credentialsMatch = server.credentials.apply(headers::get);
if (credentialsMatch != null)
{
- guard.reauthorize(server.initialId, credentialsMatch);
+ guard.reauthorize(traceId, server.routedId, server.initialId, credentialsMatch);
}
server.doEncodeHeaders(traceId, authorization, budgetId, headers204);
}
@@ -1055,7 +1058,7 @@ else if (!isCorsRequestAllowed(server.binding, headers))
final String credentialsMatch = server.credentials.apply(headers::get);
if (credentialsMatch != null)
{
- exchangeAuth = guard.reauthorize(server.initialId, credentialsMatch);
+ exchangeAuth = guard.reauthorize(traceId, server.routedId, server.initialId, credentialsMatch);
}
}
@@ -2268,6 +2271,7 @@ private boolean onDecodeHeaders(
final HttpHeaderFW connection = beginEx.headers().matchFirst(h -> HEADER_CONNECTION.equals(h.name()));
exchange.responseClosing = connection != null && connectionClose.reset(connection.value().asString()).matches();
+ event.requestAccepted(traceId, routedId, guard, authorization, beginEx.headers());
this.exchange = exchange;
}
return headersValid;
@@ -4923,7 +4927,7 @@ else if (headersDecoder.httpError())
else
{
final Map headers = headersDecoder.headers;
-
+ event.requestAccepted(traceId, routedId, guard, authorization, headers);
if (isCorsPreflightRequest(headers))
{
if (!endRequest)
@@ -4956,7 +4960,7 @@ else if (!isCorsRequestAllowed(binding, headers))
final String credentialsMatch = credentials.apply(headers::get);
if (credentialsMatch != null)
{
- guard.reauthorize(initialId, credentialsMatch);
+ guard.reauthorize(traceId, routedId, initialId, credentialsMatch);
}
doEncodeHeaders(traceId, authorization, streamId, headers204, true);
}
@@ -4968,7 +4972,7 @@ else if (!isCorsRequestAllowed(binding, headers))
final String credentialsMatch = credentials.apply(headers::get);
if (credentialsMatch != null)
{
- exchangeAuth = guard.reauthorize(initialId, credentialsMatch);
+ exchangeAuth = guard.reauthorize(traceId, routedId, initialId, credentialsMatch);
}
}
@@ -5342,7 +5346,7 @@ private void doEncodePromise(
final String credentialsMatch = credentials.apply(headers::get);
if (credentialsMatch != null)
{
- exchangeAuth = guard.reauthorize(initialId, credentialsMatch);
+ exchangeAuth = guard.reauthorize(traceId, routedId, initialId, credentialsMatch);
}
}
diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/KafkaEventContext.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/KafkaEventContext.java
new file mode 100644
index 0000000000..86e8bc3168
--- /dev/null
+++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/KafkaEventContext.java
@@ -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.
+ */
+package io.aklivity.zilla.runtime.binding.kafka.internal;
+
+import java.nio.ByteBuffer;
+import java.time.Clock;
+
+import org.agrona.MutableDirectBuffer;
+import org.agrona.concurrent.UnsafeBuffer;
+
+import io.aklivity.zilla.runtime.binding.kafka.internal.types.event.KafkaEventFW;
+import io.aklivity.zilla.runtime.engine.EngineContext;
+import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer;
+
+public class KafkaEventContext
+{
+ private static final int EVENT_BUFFER_CAPACITY = 1024;
+ private static final int ERROR_NONE = 0;
+
+ private final KafkaEventFW.Builder kafkaEventRW = new KafkaEventFW.Builder();
+ private final MutableDirectBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY));
+ private final int kafkaTypeId;
+ private final MessageConsumer eventWriter;
+ private final Clock clock;
+
+ public KafkaEventContext(
+ EngineContext context)
+ {
+ this.kafkaTypeId = context.supplyTypeId(KafkaBinding.NAME);
+ this.eventWriter = context.supplyEventWriter();
+ this.clock = context.clock();
+ }
+
+ public void authorizationFailed(
+ long traceId,
+ long bindingId)
+ {
+ KafkaEventFW event = kafkaEventRW
+ .wrap(eventBuffer, 0, eventBuffer.capacity())
+ .authorizationFailed(e -> e
+ .timestamp(clock.millis())
+ .traceId(traceId)
+ .namespacedId(bindingId)
+ )
+ .build();
+ eventWriter.accept(kafkaTypeId, event.buffer(), event.offset(), event.limit());
+ }
+
+ public void apiVersionRejected(
+ long traceId,
+ long bindingId,
+ int apiKey,
+ int apiVersion)
+ {
+ KafkaEventFW event = kafkaEventRW
+ .wrap(eventBuffer, 0, eventBuffer.capacity())
+ .apiVersionRejected(e -> e
+ .timestamp(clock.millis())
+ .traceId(traceId)
+ .namespacedId(bindingId)
+ .apiKey(apiKey)
+ .apiVersion(apiVersion)
+ )
+ .build();
+ eventWriter.accept(kafkaTypeId, event.buffer(), event.offset(), event.limit());
+ }
+}
diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientDescribeFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientDescribeFactory.java
index f1eaf5b83b..95c1a8dc53 100644
--- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientDescribeFactory.java
+++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientDescribeFactory.java
@@ -965,6 +965,7 @@ public void onDecodeResource(
assert resource.equals(this.topic);
break;
default:
+ onDecodeResponseErrorCode(traceId, originId, errorCode);
final KafkaResetExFW resetEx = kafkaResetExRW.wrap(extBuffer, 0, extBuffer.capacity())
.typeId(kafkaTypeId)
.error(errorCode)
@@ -975,6 +976,15 @@ public void onDecodeResource(
}
}
+ private void onDecodeResponseErrorCode(
+ long traceId,
+ long originId,
+ int errorCode)
+ {
+ super.onDecodeResponseErrorCode(traceId, originId, DESCRIBE_CONFIGS_API_KEY, DESCRIBE_CONFIGS_API_VERSION,
+ errorCode);
+ }
+
private void onNetwork(
int msgTypeId,
DirectBuffer buffer,
diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientFetchFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientFetchFactory.java
index f71de6de0d..b44b82d71c 100644
--- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientFetchFactory.java
+++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientFetchFactory.java
@@ -2949,6 +2949,7 @@ private void onDecodeOffsetsPartition(
this.nextOffset = partitionOffset;
break;
default:
+ onDecodeResponseErrorCode(traceId, originId, errorCode);
cleanupApplication(traceId, errorCode);
doNetworkEnd(traceId, authorization);
break;
@@ -2988,6 +2989,10 @@ private void onDecodeFetchPartition(
traceId, authorization, 0, EMPTY_OCTETS);
}
}
+ else
+ {
+ onDecodeResponseErrorCode(traceId, originId, errorCode);
+ }
cleanupApplication(traceId, errorCode);
doNetworkEnd(traceId, authorization);
@@ -2995,6 +3000,14 @@ private void onDecodeFetchPartition(
}
}
+ private void onDecodeResponseErrorCode(
+ long traceId,
+ long originId,
+ int errorCode)
+ {
+ super.onDecodeResponseErrorCode(traceId, originId, FETCH_API_KEY, FETCH_API_VERSION, errorCode);
+ }
+
private void onDecodeFetchTransactionAbort(
long traceId,
long authorization,
diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientGroupFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientGroupFactory.java
index ba89f747c1..dfb7a72bd3 100644
--- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientGroupFactory.java
+++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientGroupFactory.java
@@ -878,6 +878,8 @@ private int decodeFindCoordinatorResponse(
findCoordinatorResponse.host(), findCoordinatorResponse.port());
break;
default:
+ client.onDecodeResponseErrorCode(traceId, client.originId, FIND_COORDINATOR_API_KEY,
+ FIND_COORDINATOR_API_VERSION, errorCode);
client.errorCode = errorCode;
client.decoder = decodeClusterReject;
break;
@@ -1012,6 +1014,7 @@ private int decodeJoinGroupResponse(
joinGroupResponse.memberId().asString());
break;
default:
+ client.onDecodeResponseErrorCode(traceId, client.originId, JOIN_GROUP_API_KEY, JOIN_GROUP_VERSION, errorCode);
client.errorCode = errorCode;
client.decoder = decodeCoordinatorReject;
break;
@@ -1071,6 +1074,7 @@ private int decodeSyncGroupResponse(
client.onSyncGroupResponse(traceId, authorization, syncGroupResponse.assignment());
break;
default:
+ client.onDecodeResponseErrorCode(traceId, client.originId, SYNC_GROUP_API_KEY, SYNC_GROUP_VERSION, errorCode);
client.errorCode = errorCode;
client.decoder = decodeCoordinatorReject;
break;
@@ -1133,6 +1137,7 @@ private int decodeHeartbeatResponse(
client.onHeartbeatResponse(traceId, authorization);
break;
default:
+ client.onDecodeResponseErrorCode(traceId, client.originId, HEARTBEAT_API_KEY, HEARTBEAT_VERSION, errorCode);
client.errorCode = errorCode;
client.decoder = decodeCoordinatorReject;
break;
@@ -1197,6 +1202,8 @@ private int decodeLeaveGroupResponse(
}
else
{
+ client.onDecodeResponseErrorCode(traceId, client.originId, LEAVE_GROUP_API_KEY,
+ LEAVE_GROUP_VERSION, errorCode);
client.errorCode = errorCode;
client.decoder = decodeCoordinatorReject;
}
@@ -1211,6 +1218,8 @@ private int decodeLeaveGroupResponse(
}
else
{
+ client.onDecodeResponseErrorCode(traceId, client.originId, LEAVE_GROUP_API_KEY, LEAVE_GROUP_VERSION,
+ errorCode);
client.errorCode = errorCode;
client.decoder = decodeCoordinatorReject;
}
@@ -2565,11 +2574,20 @@ public void onDecodeResource(
assert resource.equals(delegate.nodeId);
break;
default:
+ onDecodeResponseErrorCode(traceId, originId, errorCode);
onNetworkError(traceId, errorCode);
break;
}
}
+ private void onDecodeResponseErrorCode(
+ long traceId,
+ long originId,
+ int errorCode)
+ {
+ super.onDecodeResponseErrorCode(traceId, originId, DESCRIBE_CONFIGS_API_KEY, DESCRIBE_CONFIGS_API_VERSION, errorCode);
+ }
+
private void onNetwork(
int msgTypeId,
DirectBuffer buffer,
diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientMetaFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientMetaFactory.java
index 94d2598daf..13c016633d 100644
--- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientMetaFactory.java
+++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientMetaFactory.java
@@ -1810,6 +1810,7 @@ private void onDecodeTopic(
newPartitions.clear();
break;
default:
+ onDecodeResponseErrorCode(traceId, originId, errorCode);
final KafkaResetExFW resetEx = kafkaResetExRW.wrap(extBuffer, 0, extBuffer.capacity())
.typeId(kafkaTypeId)
.error(errorCode)
@@ -1830,6 +1831,18 @@ private void onDecodePartition(
{
newPartitions.put(partitionId, leaderId);
}
+ else
+ {
+ onDecodeResponseErrorCode(traceId, originId, partitionError);
+ }
+ }
+
+ private void onDecodeResponseErrorCode(
+ long traceId,
+ long originId,
+ int errorCode)
+ {
+ super.onDecodeResponseErrorCode(traceId, originId, METADATA_API_KEY, METADATA_API_VERSION, errorCode);
}
@Override
diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetCommitFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetCommitFactory.java
index 7fec24693c..b7ca22b1f8 100644
--- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetCommitFactory.java
+++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetCommitFactory.java
@@ -589,6 +589,8 @@ private int decodeOffsetCommitPartition(
}
else
{
+ client.onDecodeResponseErrorCode(traceId, client.originId, OFFSET_COMMIT_API_KEY, OFFSET_COMMIT_API_VERSION,
+ errorCode);
client.errorCode = errorCode;
client.decoder = decodeReject;
}
diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetFetchFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetFetchFactory.java
index 35f415c6f7..4e753c0789 100644
--- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetFetchFactory.java
+++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetFetchFactory.java
@@ -661,6 +661,8 @@ private int decodeOffsetFetchPartition(
client.decoder = decodeOffsetFetchPartitions;
break;
default:
+ client.onDecodeResponseErrorCode(traceId, client.originId, OFFSET_FETCH_API_KEY, OFFSET_FETCH_API_VERSION,
+ errorCode);
client.errorCode = errorCode;
client.decoder = decodeReject;
break;
diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientProduceFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientProduceFactory.java
index d21855ab20..9be9f026d2 100644
--- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientProduceFactory.java
+++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientProduceFactory.java
@@ -42,6 +42,7 @@
import io.aklivity.zilla.runtime.binding.kafka.config.KafkaServerConfig;
import io.aklivity.zilla.runtime.binding.kafka.internal.KafkaBinding;
import io.aklivity.zilla.runtime.binding.kafka.internal.KafkaConfiguration;
+import io.aklivity.zilla.runtime.binding.kafka.internal.KafkaEventContext;
import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaBindingConfig;
import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaRouteConfig;
import io.aklivity.zilla.runtime.binding.kafka.internal.types.Array32FW;
@@ -193,6 +194,7 @@ public final class KafkaClientProduceFactory extends KafkaClientSaslHandshaker i
private final int decodeMaxBytes;
private final int encodeMaxBytes;
private final CRC32C crc32c;
+ private final KafkaEventContext event;
public KafkaClientProduceFactory(
KafkaConfiguration config,
@@ -218,6 +220,7 @@ public KafkaClientProduceFactory(
this.encodeMaxBytes = Math.min(config.clientProduceMaxBytes(),
encodePool.slotCapacity() - PRODUCE_REQUEST_RECORDS_OFFSET_MAX);
this.crc32c = new CRC32C();
+ this.event = new KafkaEventContext(context);
}
@Override
@@ -2268,6 +2271,7 @@ private void onDecodeProducePartition(
assert partitionId == this.partitionId;
break;
default:
+ onDecodeResponseErrorCode(traceId, originId, errorCode);
final KafkaResetExFW resetEx = kafkaResetExRW.wrap(extBuffer, 0, extBuffer.capacity())
.typeId(kafkaTypeId)
.error(errorCode)
@@ -2278,6 +2282,14 @@ private void onDecodeProducePartition(
}
}
+ private void onDecodeResponseErrorCode(
+ long traceId,
+ long originId,
+ int errorCode)
+ {
+ super.onDecodeResponseErrorCode(traceId, originId, PRODUCE_API_KEY, PRODUCE_API_VERSION, errorCode);
+ }
+
@Override
protected void onDecodeSaslResponse(
long traceId)
diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientSaslHandshaker.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientSaslHandshaker.java
index b6a3b266c4..ca61063380 100644
--- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientSaslHandshaker.java
+++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientSaslHandshaker.java
@@ -43,6 +43,7 @@
import io.aklivity.zilla.runtime.binding.kafka.config.KafkaServerConfig;
import io.aklivity.zilla.runtime.binding.kafka.identity.KafkaClientIdSupplier;
import io.aklivity.zilla.runtime.binding.kafka.internal.KafkaConfiguration;
+import io.aklivity.zilla.runtime.binding.kafka.internal.KafkaEventContext;
import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaScramMechanism;
import io.aklivity.zilla.runtime.binding.kafka.internal.types.String16FW;
import io.aklivity.zilla.runtime.binding.kafka.internal.types.codec.RequestHeaderFW;
@@ -64,6 +65,7 @@ public abstract class KafkaClientSaslHandshaker
private static final short SASL_AUTHENTICATE_API_VERSION = 1;
private static final int ERROR_SASL_AUTHENTICATION_FAILED = 58;
private static final int ERROR_NONE = 0;
+ private static final int ERROR_UNSUPPORTED_VERSION = 35;
private static final String CLIENT_KEY = "Client Key";
private static final String SERVER_KEY = "Server Key";
@@ -92,6 +94,7 @@ public abstract class KafkaClientSaslHandshaker
private final SaslHandshakeResponseFW saslHandshakeResponseRO = new SaslHandshakeResponseFW();
private final SaslHandshakeMechanismResponseFW saslHandshakeMechanismResponseRO = new SaslHandshakeMechanismResponseFW();
private final SaslAuthenticateResponseFW saslAuthenticateResponseRO = new SaslAuthenticateResponseFW();
+ private final KafkaEventContext event;
private KafkaSaslClientDecoder decodeSaslPlainAuthenticate = this::decodeSaslPlainAuthenticate;
private KafkaSaslClientDecoder decodeSaslScramAuthenticateFirst = this::decodeSaslScramAuthenticateFirst;
@@ -125,6 +128,7 @@ public KafkaClientSaslHandshaker(
this.writeBuffer = new UnsafeBuffer(new byte[context.writeBuffer().capacity()]);
this.nonceSupplier = config.nonceSupplier();
this.clientIdsByServer = new Object2ObjectHashMap<>();
+ this.event = new KafkaEventContext(context);
}
public abstract class KafkaSaslClient
@@ -423,6 +427,19 @@ private void doEncodeSaslScramFinalAuthenticateRequest(
doDecodeSaslAuthenticateResponse(traceId);
}
+ protected final void onDecodeResponseErrorCode(
+ long traceId,
+ long bindingId,
+ int apiKey,
+ int apiVersion,
+ int errorCode)
+ {
+ if (errorCode == ERROR_UNSUPPORTED_VERSION)
+ {
+ event.apiVersionRejected(traceId, bindingId, apiKey, apiVersion);
+ }
+ }
+
protected abstract void doNetworkData(
long traceId,
long budgetId,
@@ -689,6 +706,10 @@ private int decodeSaslPlainAuthenticate(
if (authenticateResponse != null)
{
final int errorCode = authenticateResponse.errorCode();
+ if (errorCode != ERROR_NONE)
+ {
+ event.authorizationFailed(traceId, client.originId);
+ }
progress = authenticateResponse.limit();
@@ -723,6 +744,10 @@ private int decodeSaslScramAuthenticateFirst(
if (authenticateResponse != null)
{
final int errorCode = authenticateResponse.errorCode();
+ if (errorCode != ERROR_NONE)
+ {
+ event.authorizationFailed(traceId, client.originId);
+ }
progress = authenticateResponse.limit();
diff --git a/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/MqttServerFactory.java b/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/MqttServerFactory.java
index 1c681d82ca..7d8e6a7f5b 100644
--- a/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/MqttServerFactory.java
+++ b/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/MqttServerFactory.java
@@ -2910,7 +2910,7 @@ else if (this.authField.equals(MqttConnectProperty.PASSWORD))
if (credentialsMatch != null)
{
- sessionAuth = guard.reauthorize(initialId, credentialsMatch);
+ sessionAuth = guard.reauthorize(traceId, routedId, initialId, credentialsMatch);
}
}
diff --git a/runtime/binding-tcp/pom.xml b/runtime/binding-tcp/pom.xml
index c28cdc4b4e..513e5eae6b 100644
--- a/runtime/binding-tcp/pom.xml
+++ b/runtime/binding-tcp/pom.xml
@@ -26,7 +26,7 @@
11
11
- 0.89
+ 0.88
0
@@ -123,7 +123,7 @@
flyweight-maven-plugin
${project.version}
- core proxy
+ core tcp proxy
io.aklivity.zilla.runtime.binding.tcp.internal.types
diff --git a/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/TcpEventContext.java b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/TcpEventContext.java
new file mode 100644
index 0000000000..7159725444
--- /dev/null
+++ b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/TcpEventContext.java
@@ -0,0 +1,62 @@
+/*
+ * 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.tcp.internal;
+
+import java.nio.ByteBuffer;
+import java.time.Clock;
+
+import org.agrona.MutableDirectBuffer;
+import org.agrona.concurrent.UnsafeBuffer;
+
+import io.aklivity.zilla.runtime.binding.tcp.internal.types.event.TcpEventFW;
+import io.aklivity.zilla.runtime.engine.EngineContext;
+import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer;
+
+public class TcpEventContext
+{
+ private static final int EVENT_BUFFER_CAPACITY = 1024;
+
+ private final TcpEventFW.Builder tcpEventRW = new TcpEventFW.Builder();
+ private final MutableDirectBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY));
+ private final int tcpTypeId;
+ private final MessageConsumer eventWriter;
+ private final Clock clock;
+
+ public TcpEventContext(
+ EngineContext context)
+ {
+ this.tcpTypeId = context.supplyTypeId(TcpBinding.NAME);
+ this.eventWriter = context.supplyEventWriter();
+ this.clock = context.clock();
+ }
+
+ public void dnsResolutionFailed(
+ long traceId,
+ long bindingId,
+ String address)
+ {
+ TcpEventFW event = tcpEventRW
+ .wrap(eventBuffer, 0, eventBuffer.capacity())
+ .dnsFailed(e -> e
+ .timestamp(clock.millis())
+ .traceId(traceId)
+ .namespacedId(bindingId)
+ .address(address)
+ )
+ .build();
+ eventWriter.accept(tcpTypeId, event.buffer(), event.offset(), event.limit());
+ }
+}
diff --git a/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientFactory.java b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientFactory.java
index 08afe896f8..cc3d8645f9 100644
--- a/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientFactory.java
+++ b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientFactory.java
@@ -138,6 +138,7 @@ public MessageConsumer newStream(
MessageConsumer application)
{
final BeginFW begin = beginRO.wrap(buffer, index, index + length);
+ final long traceId = begin.traceId();
final long originId = begin.originId();
final long routedId = begin.routedId();
final long authorization = begin.authorization();
@@ -151,7 +152,7 @@ public MessageConsumer newStream(
TcpBindingConfig binding = router.lookup(routedId);
if (binding != null)
{
- route = router.resolve(binding, authorization, beginEx);
+ route = router.resolve(binding, traceId, authorization, beginEx);
}
MessageConsumer newStream = null;
@@ -257,13 +258,14 @@ private void doNetConnect(
state = TcpState.openingInitial(state);
net.setOption(SO_KEEPALIVE, options != null && options.keepalive);
+ networkKey = supplyPollerKey.apply(net);
+
if (net.connect(remoteAddress))
{
onNetConnected();
}
else
{
- networkKey = supplyPollerKey.apply(net);
networkKey.handler(OP_CONNECT, this::onNetConnect);
networkKey.register(OP_CONNECT);
}
@@ -283,7 +285,7 @@ private int onNetConnect(
net.finishConnect();
onNetConnected();
}
- catch (UnresolvedAddressException | IOException ex)
+ catch (IOException ex)
{
onNetRejected();
}
diff --git a/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientRouter.java b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientRouter.java
index ecfb709157..fc343d4e2b 100644
--- a/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientRouter.java
+++ b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientRouter.java
@@ -30,6 +30,7 @@
import org.agrona.collections.Long2ObjectHashMap;
import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig;
+import io.aklivity.zilla.runtime.binding.tcp.internal.TcpEventContext;
import io.aklivity.zilla.runtime.binding.tcp.internal.config.TcpBindingConfig;
import io.aklivity.zilla.runtime.binding.tcp.internal.config.TcpRouteConfig;
import io.aklivity.zilla.runtime.binding.tcp.internal.types.Array32FW;
@@ -49,12 +50,14 @@ public final class TcpClientRouter
private final Function resolveHost;
private final Long2ObjectHashMap bindings;
+ private final TcpEventContext event;
public TcpClientRouter(
EngineContext context)
{
this.resolveHost = context::resolveHost;
this.bindings = new Long2ObjectHashMap<>();
+ this.event = new TcpEventContext(context);
}
public void attach(
@@ -71,6 +74,7 @@ public TcpBindingConfig lookup(
public InetSocketAddress resolve(
TcpBindingConfig binding,
+ long traceId,
long authorization,
ProxyBeginExFW beginEx)
{
@@ -79,86 +83,106 @@ public InetSocketAddress resolve(
InetSocketAddress resolved = null;
- if (beginEx == null)
- {
- resolved = options != null ? new InetSocketAddress(options.host, port) : null;
- }
- else if (binding.routes == TcpBindingConfig.DEFAULT_CLIENT_ROUTES)
- {
- ProxyAddressFW address = beginEx.address();
- resolved = resolveInetSocketAddress(address);
- }
- else
+ try
{
- final ProxyAddressFW address = beginEx.address();
-
- for (TcpRouteConfig route : binding.routes)
+ if (beginEx == null)
{
- if (!route.authorized(authorization))
- {
- continue;
- }
+ InetAddress[] addresses = options != null ? resolveHost(options.host) : null;
+ resolved = addresses != null ? new InetSocketAddress(addresses[0], port) : null;
+ }
+ else if (binding.routes == TcpBindingConfig.DEFAULT_CLIENT_ROUTES)
+ {
+ ProxyAddressFW address = beginEx.address();
+ resolved = resolveInetSocketAddress(address);
+ }
+ else
+ {
+ final ProxyAddressFW address = beginEx.address();
- Array32FW infos = beginEx.infos();
- ProxyInfoFW authorityInfo = infos.matchFirst(i -> i.kind() == AUTHORITY);
- if (authorityInfo != null && route.matchesExplicit(r -> r.authority != null))
+ for (TcpRouteConfig route : binding.routes)
{
- final List authorities = Arrays
- .stream(resolveHost.apply(authorityInfo.authority().asString()))
- .map(a -> new InetSocketAddress(a, port))
- .collect(Collectors.toList());
+ if (!route.authorized(authorization))
+ {
+ continue;
+ }
- for (InetSocketAddress authority : authorities)
+ Array32FW infos = beginEx.infos();
+ ProxyInfoFW authorityInfo = infos.matchFirst(i -> i.kind() == AUTHORITY);
+ if (authorityInfo != null && route.matchesExplicit(r -> r.authority != null))
{
- if (route.matchesExplicit(authority))
+ final List authorities = Arrays
+ .stream(resolveHost(authorityInfo.authority().asString()))
+ .map(a -> new InetSocketAddress(a, port))
+ .collect(Collectors.toList());
+
+ for (InetSocketAddress authority : authorities)
{
- resolved = authority;
- break;
+ if (route.matchesExplicit(authority))
+ {
+ resolved = authority;
+ break;
+ }
}
}
- }
- if (resolved == null)
- {
- resolved = resolve(address, authorization, route::matchesExplicit);
- }
+ if (resolved == null)
+ {
+ resolved = resolve(address, authorization, route::matchesExplicit);
+ }
- if (resolved != null)
- {
- break;
+ if (resolved != null)
+ {
+ break;
+ }
}
- }
-
- if (resolved == null &&
- options != null &&
- options.host != null &&
- !"*".equals(options.host))
- {
- final List host = Arrays
- .stream(resolveHost.apply(options.host))
- .map(a -> new InetSocketAddress(a, port))
- .collect(Collectors.toList());
- for (TcpRouteConfig route : binding.routes)
+ if (resolved == null &&
+ options != null &&
+ options.host != null &&
+ !"*".equals(options.host))
{
- if (!route.authorized(authorization))
+ final List host = Arrays
+ .stream(resolveHost(options.host))
+ .map(a -> new InetSocketAddress(a, port))
+ .collect(Collectors.toList());
+
+ for (TcpRouteConfig route : binding.routes)
{
- continue;
- }
+ if (!route.authorized(authorization))
+ {
+ continue;
+ }
- resolved = resolve(address, authorization, host::contains);
+ resolved = resolve(address, authorization, host::contains);
- if (resolved != null)
- {
- break;
+ if (resolved != null)
+ {
+ break;
+ }
}
}
}
}
-
+ catch (TcpDnsFailedException ex)
+ {
+ event.dnsResolutionFailed(traceId, binding.id, ex.hostname);
+ }
return resolved;
}
+ private InetAddress[] resolveHost(
+ String hostname)
+ {
+ try
+ {
+ return resolveHost.apply(hostname);
+ }
+ catch (Throwable ex)
+ {
+ throw new TcpDnsFailedException(ex, hostname);
+ }
+ }
+
public void detach(
long bindingId)
{
@@ -291,4 +315,17 @@ private InetSocketAddress resolveInetSocketAddress(
return resolved;
}
+
+ private static final class TcpDnsFailedException extends RuntimeException
+ {
+ private final String hostname;
+
+ TcpDnsFailedException(
+ Throwable cause,
+ String hostname)
+ {
+ super(cause);
+ this.hostname = hostname;
+ }
+ }
}
diff --git a/runtime/binding-tcp/src/test/java/io/aklivity/zilla/runtime/binding/tcp/internal/streams/ClientIT.java b/runtime/binding-tcp/src/test/java/io/aklivity/zilla/runtime/binding/tcp/internal/streams/ClientIT.java
index cd69d87413..5373187431 100644
--- a/runtime/binding-tcp/src/test/java/io/aklivity/zilla/runtime/binding/tcp/internal/streams/ClientIT.java
+++ b/runtime/binding-tcp/src/test/java/io/aklivity/zilla/runtime/binding/tcp/internal/streams/ClientIT.java
@@ -21,7 +21,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.rules.RuleChain.outerRule;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
@@ -37,6 +39,7 @@
import io.aklivity.zilla.runtime.engine.test.EngineRule;
import io.aklivity.zilla.runtime.engine.test.annotation.Configuration;
+import io.aklivity.zilla.runtime.engine.test.annotation.Configure;
public class ClientIT
{
@@ -215,6 +218,18 @@ public void connnectionFailed() throws Exception
k3po.finish();
}
+ @Test
+ @Configuration("client.host.yaml")
+ @Specification({
+ "${app}/connection.failed/client"
+ })
+ @Configure(name = "zilla.engine.host.resolver",
+ value = "io.aklivity.zilla.runtime.binding.tcp.internal.streams.ClientIT::resolveHost")
+ public void dnsResolutionFailed() throws Exception
+ {
+ k3po.finish();
+ }
+
@Test
@Configuration("client.host.yaml")
@Specification({
@@ -326,4 +341,10 @@ public void shouldWriteDataAfterReceiveEnd() throws Exception
}
}
}
+
+ public static InetAddress[] resolveHost(
+ String host) throws UnknownHostException
+ {
+ throw new UnknownHostException();
+ }
}
diff --git a/runtime/binding-tls/pom.xml b/runtime/binding-tls/pom.xml
index 6b6779e140..1fd6bcd4d1 100644
--- a/runtime/binding-tls/pom.xml
+++ b/runtime/binding-tls/pom.xml
@@ -26,7 +26,7 @@
11
11
- 0.76
+ 0.75
0
@@ -109,7 +109,7 @@
flyweight-maven-plugin
${project.version}
- core proxy protocol
+ core proxy tls protocol
io.aklivity.zilla.runtime.binding.tls.internal.types
diff --git a/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/TlsEventContext.java b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/TlsEventContext.java
new file mode 100644
index 0000000000..22722224ea
--- /dev/null
+++ b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/TlsEventContext.java
@@ -0,0 +1,120 @@
+/*
+ * 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.tls.internal;
+
+import java.nio.ByteBuffer;
+import java.time.Clock;
+
+import org.agrona.MutableDirectBuffer;
+import org.agrona.concurrent.UnsafeBuffer;
+
+import io.aklivity.zilla.runtime.binding.tls.internal.types.event.TlsEventFW;
+import io.aklivity.zilla.runtime.engine.EngineContext;
+import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer;
+
+public class TlsEventContext
+{
+ private static final int EVENT_BUFFER_CAPACITY = 1024;
+
+ private final TlsEventFW.Builder tlsEventRW = new TlsEventFW.Builder();
+ private final MutableDirectBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY));
+ private final int tlsTypeId;
+ private final MessageConsumer eventWriter;
+ private final Clock clock;
+
+ public TlsEventContext(
+ EngineContext context)
+ {
+ this.tlsTypeId = context.supplyTypeId(TlsBinding.NAME);
+ this.eventWriter = context.supplyEventWriter();
+ this.clock = context.clock();
+ }
+
+ public void tlsFailed(
+ long traceId,
+ long bindingId)
+ {
+ TlsEventFW event = tlsEventRW
+ .wrap(eventBuffer, 0, eventBuffer.capacity())
+ .tlsFailed(e -> e
+ .timestamp(clock.millis())
+ .traceId(traceId)
+ .namespacedId(bindingId)
+ )
+ .build();
+ eventWriter.accept(tlsTypeId, event.buffer(), event.offset(), event.limit());
+ }
+
+ public void tlsProtocolRejected(
+ long traceId,
+ long bindingId)
+ {
+ TlsEventFW event = tlsEventRW
+ .wrap(eventBuffer, 0, eventBuffer.capacity())
+ .tlsProtocolRejected(e -> e
+ .timestamp(clock.millis())
+ .traceId(traceId)
+ .namespacedId(bindingId)
+ )
+ .build();
+ eventWriter.accept(tlsTypeId, event.buffer(), event.offset(), event.limit());
+ }
+
+ public void tlsKeyRejected(
+ long traceId,
+ long bindingId)
+ {
+ TlsEventFW event = tlsEventRW
+ .wrap(eventBuffer, 0, eventBuffer.capacity())
+ .tlsKeyRejected(e -> e
+ .timestamp(clock.millis())
+ .traceId(traceId)
+ .namespacedId(bindingId)
+ )
+ .build();
+ eventWriter.accept(tlsTypeId, event.buffer(), event.offset(), event.limit());
+ }
+
+ public void tlsPeerNotVerified(
+ long traceId,
+ long bindingId)
+ {
+ TlsEventFW event = tlsEventRW
+ .wrap(eventBuffer, 0, eventBuffer.capacity())
+ .tlsPeerNotVerified(e -> e
+ .timestamp(clock.millis())
+ .traceId(traceId)
+ .namespacedId(bindingId)
+ )
+ .build();
+ eventWriter.accept(tlsTypeId, event.buffer(), event.offset(), event.limit());
+ }
+
+ public void tlsHandshakeFailed(
+ long traceId,
+ long bindingId)
+ {
+ TlsEventFW event = tlsEventRW
+ .wrap(eventBuffer, 0, eventBuffer.capacity())
+ .tlsHandshakeFailed(e -> e
+ .timestamp(clock.millis())
+ .traceId(traceId)
+ .namespacedId(bindingId)
+ )
+ .build();
+ eventWriter.accept(tlsTypeId, event.buffer(), event.offset(), event.limit());
+ }
+}
diff --git a/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsClientFactory.java b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsClientFactory.java
index fd022f46d0..b111b35b77 100644
--- a/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsClientFactory.java
+++ b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsClientFactory.java
@@ -37,6 +37,10 @@
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLKeyException;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLProtocolException;
import org.agrona.DirectBuffer;
import org.agrona.MutableDirectBuffer;
@@ -44,6 +48,7 @@
import org.agrona.concurrent.UnsafeBuffer;
import io.aklivity.zilla.runtime.binding.tls.internal.TlsConfiguration;
+import io.aklivity.zilla.runtime.binding.tls.internal.TlsEventContext;
import io.aklivity.zilla.runtime.binding.tls.internal.config.TlsBindingConfig;
import io.aklivity.zilla.runtime.binding.tls.internal.config.TlsRouteConfig;
import io.aklivity.zilla.runtime.binding.tls.internal.types.OctetsFW;
@@ -129,6 +134,7 @@ public final class TlsClientFactory implements TlsStreamFactory
private final LongUnaryOperator supplyReplyId;
private final int initialPadAdjust;
private final Long2ObjectHashMap bindings;
+ private final TlsEventContext event;
private final int decodeMax;
private final int handshakeMax;
@@ -168,6 +174,7 @@ public TlsClientFactory(
this.initialPadAdjust = Math.max(context.bufferPool().slotCapacity() >> 14, 1) * MAXIMUM_HEADER_SIZE;
this.bindings = new Long2ObjectHashMap<>();
+ this.event = new TlsEventContext(context);
this.inNetByteBuffer = ByteBuffer.allocate(writeBuffer.capacity());
this.inNetBuffer = new UnsafeBuffer(inNetByteBuffer);
this.outNetByteBuffer = ByteBuffer.allocate(writeBuffer.capacity() << 1);
@@ -568,51 +575,80 @@ private int decodeNotHandshaking(
try
{
- final SSLEngineResult result = client.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer);
- final int bytesProduced = result.bytesProduced();
- final int bytesConsumed = result.bytesConsumed();
-
- switch (result.getStatus())
+ try
{
- case BUFFER_UNDERFLOW:
- case BUFFER_OVERFLOW:
- assert false;
- break;
- case OK:
- if (result.getHandshakeStatus() == HandshakeStatus.FINISHED)
+ final SSLEngineResult result = client.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer);
+ final int bytesProduced = result.bytesProduced();
+ final int bytesConsumed = result.bytesConsumed();
+
+ switch (result.getStatus())
{
- if (!client.stream.isPresent())
+ case BUFFER_UNDERFLOW:
+ case BUFFER_OVERFLOW:
+ assert false;
+ break;
+ case OK:
+ if (result.getHandshakeStatus() == HandshakeStatus.FINISHED)
{
- client.onDecodeHandshakeFinished(traceId, budgetId);
+ if (!client.stream.isPresent())
+ {
+ client.onDecodeHandshakeFinished(traceId, budgetId);
+ }
}
- }
- if (bytesProduced == 0)
- {
- client.decoder = decodeHandshake;
- progress += bytesConsumed;
- }
- else
- {
- assert bytesConsumed == tlsRecordBytes;
- assert bytesProduced <= bytesConsumed : String.format("%d <= %d", bytesProduced, bytesConsumed);
+ if (bytesProduced == 0)
+ {
+ client.decoder = decodeHandshake;
+ progress += bytesConsumed;
+ }
+ else
+ {
+ assert bytesConsumed == tlsRecordBytes;
+ assert bytesProduced <= bytesConsumed :
+ String.format("%d <= %d", bytesProduced, bytesConsumed);
- tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit)
- .payload(outAppBuffer, 0, bytesProduced)
- .build();
+ tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit)
+ .payload(outAppBuffer, 0, bytesProduced)
+ .build();
- client.decodableRecordBytes -= bytesConsumed;
- assert client.decodableRecordBytes == 0;
+ client.decodableRecordBytes -= bytesConsumed;
+ assert client.decodableRecordBytes == 0;
- client.decoder = decodeNotHandshakingUnwrapped;
+ client.decoder = decodeNotHandshakingUnwrapped;
+ }
+ break;
+ case CLOSED:
+ assert bytesProduced == 0;
+ client.onDecodeInboundClosed(traceId);
+ client.decoder = TlsState.replyClosed(client.state) ? decodeIgnoreAll : decodeHandshake;
+ progress += bytesConsumed;
+ break;
}
- break;
- case CLOSED:
- assert bytesProduced == 0;
- client.onDecodeInboundClosed(traceId);
- client.decoder = TlsState.replyClosed(client.state) ? decodeIgnoreAll : decodeHandshake;
- progress += bytesConsumed;
- break;
+ }
+ catch (SSLProtocolException ex)
+ {
+ event.tlsProtocolRejected(traceId, client.originId);
+ throw ex;
+ }
+ catch (SSLKeyException ex)
+ {
+ event.tlsKeyRejected(traceId, client.originId);
+ throw ex;
+ }
+ catch (SSLPeerUnverifiedException ex)
+ {
+ event.tlsPeerNotVerified(traceId, client.originId);
+ throw ex;
+ }
+ catch (SSLHandshakeException ex)
+ {
+ event.tlsHandshakeFailed(traceId, client.originId);
+ throw ex;
+ }
+ catch (SSLException ex)
+ {
+ event.tlsFailed(traceId, client.originId);
+ throw ex;
}
}
catch (SSLException ex)
@@ -750,37 +786,65 @@ private int decodeHandshakeNeedUnwrap(
try
{
- final SSLEngineResult result = client.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer);
- final int bytesConsumed = result.bytesConsumed();
- final int bytesProduced = result.bytesProduced();
-
- switch (result.getStatus())
+ try
{
- case BUFFER_UNDERFLOW:
- if (TlsState.replyClosed(client.state))
+ final SSLEngineResult result = client.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer);
+ final int bytesConsumed = result.bytesConsumed();
+ final int bytesProduced = result.bytesProduced();
+
+ switch (result.getStatus())
{
+ case BUFFER_UNDERFLOW:
+ if (TlsState.replyClosed(client.state))
+ {
+ client.decoder = decodeIgnoreAll;
+ }
+ break;
+ case BUFFER_OVERFLOW:
+ assert false;
+ break;
+ case OK:
+ assert bytesProduced == 0;
+ if (result.getHandshakeStatus() == HandshakeStatus.FINISHED)
+ {
+ client.onDecodeHandshakeFinished(traceId, budgetId);
+ }
+ client.decoder = decodeHandshake;
+ break;
+ case CLOSED:
+ assert bytesProduced == 0;
+ client.onDecodeInboundClosed(traceId);
client.decoder = decodeIgnoreAll;
+ break;
}
- break;
- case BUFFER_OVERFLOW:
- assert false;
- break;
- case OK:
- assert bytesProduced == 0;
- if (result.getHandshakeStatus() == HandshakeStatus.FINISHED)
- {
- client.onDecodeHandshakeFinished(traceId, budgetId);
- }
- client.decoder = decodeHandshake;
- break;
- case CLOSED:
- assert bytesProduced == 0;
- client.onDecodeInboundClosed(traceId);
- client.decoder = decodeIgnoreAll;
- break;
- }
- progress += bytesConsumed;
+ progress += bytesConsumed;
+ }
+ catch (SSLProtocolException ex)
+ {
+ event.tlsProtocolRejected(traceId, client.originId);
+ throw ex;
+ }
+ catch (SSLKeyException ex)
+ {
+ event.tlsKeyRejected(traceId, client.originId);
+ throw ex;
+ }
+ catch (SSLPeerUnverifiedException ex)
+ {
+ event.tlsPeerNotVerified(traceId, client.originId);
+ throw ex;
+ }
+ catch (SSLHandshakeException ex)
+ {
+ event.tlsHandshakeFailed(traceId, client.originId);
+ throw ex;
+ }
+ catch (SSLException ex)
+ {
+ event.tlsFailed(traceId, client.originId);
+ throw ex;
+ }
}
catch (SSLException ex)
{
@@ -1631,6 +1695,7 @@ private void onNetSignalHandshakeTimeout(
final long traceId = signal.traceId();
cleanupNet(traceId);
+ event.tlsHandshakeFailed(traceId, client.originId);
decoder = decodeIgnoreAll;
}
}
@@ -1647,7 +1712,35 @@ private void doNetBegin(
try
{
- tlsEngine.beginHandshake();
+ try
+ {
+ tlsEngine.beginHandshake();
+ }
+ catch (SSLProtocolException ex)
+ {
+ event.tlsProtocolRejected(traceId, client.originId);
+ throw ex;
+ }
+ catch (SSLKeyException ex)
+ {
+ event.tlsKeyRejected(traceId, client.originId);
+ throw ex;
+ }
+ catch (SSLPeerUnverifiedException ex)
+ {
+ event.tlsPeerNotVerified(traceId, client.originId);
+ throw ex;
+ }
+ catch (SSLHandshakeException ex)
+ {
+ event.tlsHandshakeFailed(traceId, client.originId);
+ throw ex;
+ }
+ catch (SSLException ex)
+ {
+ event.tlsFailed(traceId, client.originId);
+ throw ex;
+ }
}
catch (SSLException ex)
{
@@ -1998,38 +2091,66 @@ private void doEncodeWrap(
try
{
- loop:
- do
+ try
{
- final SSLEngineResult result = tlsEngine.wrap(inAppByteBuffer, outNetByteBuffer);
- final int bytesProduced = result.bytesProduced();
-
- switch (result.getStatus())
+ loop:
+ do
{
- case BUFFER_OVERFLOW:
- case BUFFER_UNDERFLOW:
- assert false;
- break;
- case CLOSED:
- assert bytesProduced > 0;
- doAppReset(traceId);
- state = TlsState.closingReply(state);
- break loop;
- case OK:
- assert bytesProduced > 0 || tlsEngine.isInboundDone();
- if (result.getHandshakeStatus() == HandshakeStatus.FINISHED)
+ final SSLEngineResult result = tlsEngine.wrap(inAppByteBuffer, outNetByteBuffer);
+ final int bytesProduced = result.bytesProduced();
+
+ switch (result.getStatus())
{
- if (proactiveReplyBegin)
+ case BUFFER_OVERFLOW:
+ case BUFFER_UNDERFLOW:
+ assert false;
+ break;
+ case CLOSED:
+ assert bytesProduced > 0;
+ doAppReset(traceId);
+ state = TlsState.closingReply(state);
+ break loop;
+ case OK:
+ assert bytesProduced > 0 || tlsEngine.isInboundDone();
+ if (result.getHandshakeStatus() == HandshakeStatus.FINISHED)
{
- onDecodeHandshakeFinished(traceId, budgetId);
+ if (proactiveReplyBegin)
+ {
+ onDecodeHandshakeFinished(traceId, budgetId);
+ }
}
+ break;
}
- break;
- }
- } while (inAppByteBuffer.hasRemaining());
+ } while (inAppByteBuffer.hasRemaining());
- final int outNetBytesProduced = outNetByteBuffer.position();
- doNetData(traceId, budgetId, outNetBuffer, 0, outNetBytesProduced);
+ final int outNetBytesProduced = outNetByteBuffer.position();
+ doNetData(traceId, budgetId, outNetBuffer, 0, outNetBytesProduced);
+ }
+ catch (SSLProtocolException ex)
+ {
+ event.tlsProtocolRejected(traceId, client.originId);
+ throw ex;
+ }
+ catch (SSLKeyException ex)
+ {
+ event.tlsKeyRejected(traceId, client.originId);
+ throw ex;
+ }
+ catch (SSLPeerUnverifiedException ex)
+ {
+ event.tlsPeerNotVerified(traceId, client.originId);
+ throw ex;
+ }
+ catch (SSLHandshakeException ex)
+ {
+ event.tlsHandshakeFailed(traceId, client.originId);
+ throw ex;
+ }
+ catch (SSLException ex)
+ {
+ event.tlsFailed(traceId, client.originId);
+ throw ex;
+ }
}
catch (SSLException ex)
{
diff --git a/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsServerFactory.java b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsServerFactory.java
index f78ccc88fb..91d30e9b3d 100644
--- a/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsServerFactory.java
+++ b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsServerFactory.java
@@ -42,7 +42,10 @@
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLKeyException;
import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLProtocolException;
import javax.net.ssl.SSLSession;
import javax.security.auth.x500.X500Principal;
@@ -52,6 +55,7 @@
import org.agrona.concurrent.UnsafeBuffer;
import io.aklivity.zilla.runtime.binding.tls.internal.TlsConfiguration;
+import io.aklivity.zilla.runtime.binding.tls.internal.TlsEventContext;
import io.aklivity.zilla.runtime.binding.tls.internal.config.TlsBindingConfig;
import io.aklivity.zilla.runtime.binding.tls.internal.config.TlsRouteConfig;
import io.aklivity.zilla.runtime.binding.tls.internal.types.OctetsFW;
@@ -145,6 +149,7 @@ public final class TlsServerFactory implements TlsStreamFactory
private final LongUnaryOperator supplyReplyId;
private final int replyPadAdjust;
private final Long2ObjectHashMap bindings;
+ private final TlsEventContext event;
private final int decodeMax;
private final int handshakeMax;
@@ -183,6 +188,7 @@ public TlsServerFactory(
this.handshakeMax = Math.min(config.handshakeWindowBytes(), decodeMax);
this.handshakeTimeoutMillis = SECONDS.toMillis(config.handshakeTimeout());
this.bindings = new Long2ObjectHashMap<>();
+ this.event = new TlsEventContext(context);
this.inNetByteBuffer = ByteBuffer.allocate(writeBuffer.capacity());
this.inNetBuffer = new UnsafeBuffer(inNetByteBuffer);
@@ -523,8 +529,36 @@ private int decodeBeforeHandshake(
{
try
{
- server.tlsEngine.beginHandshake();
- server.decoder = decodeHandshake;
+ try
+ {
+ server.tlsEngine.beginHandshake();
+ server.decoder = decodeHandshake;
+ }
+ catch (SSLProtocolException ex)
+ {
+ event.tlsProtocolRejected(traceId, server.routedId);
+ throw ex;
+ }
+ catch (SSLKeyException ex)
+ {
+ event.tlsKeyRejected(traceId, server.routedId);
+ throw ex;
+ }
+ catch (SSLPeerUnverifiedException ex)
+ {
+ event.tlsPeerNotVerified(traceId, server.routedId);
+ throw ex;
+ }
+ catch (SSLHandshakeException ex)
+ {
+ event.tlsHandshakeFailed(traceId, server.routedId);
+ throw ex;
+ }
+ catch (SSLException | RuntimeException ex)
+ {
+ event.tlsFailed(traceId, server.routedId);
+ throw ex;
+ }
}
catch (SSLException | RuntimeException ex)
{
@@ -605,43 +639,72 @@ private int decodeNotHandshaking(
try
{
- final SSLEngineResult result = server.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer);
- final int bytesProduced = result.bytesProduced();
- final int bytesConsumed = result.bytesConsumed();
-
- switch (result.getStatus())
+ try
{
- case BUFFER_UNDERFLOW:
- case BUFFER_OVERFLOW:
- assert false;
- break;
- case OK:
- if (bytesProduced == 0)
+ final SSLEngineResult result = server.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer);
+ final int bytesProduced = result.bytesProduced();
+ final int bytesConsumed = result.bytesConsumed();
+
+ switch (result.getStatus())
{
- server.decoder = decodeHandshake;
+ case BUFFER_UNDERFLOW:
+ case BUFFER_OVERFLOW:
+ assert false;
+ break;
+ case OK:
+ if (bytesProduced == 0)
+ {
+ server.decoder = decodeHandshake;
+ progress += bytesConsumed;
+ }
+ else
+ {
+ assert bytesConsumed == tlsRecordBytes;
+ assert bytesProduced <= bytesConsumed :
+ String.format("%d <= %d", bytesProduced, bytesConsumed);
+
+ tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit)
+ .payload(outAppBuffer, 0, bytesProduced)
+ .build();
+
+ server.decodableRecordBytes -= bytesConsumed;
+ assert server.decodableRecordBytes == 0;
+
+ server.decoder = decodeNotHandshakingUnwrapped;
+ }
+ break;
+ case CLOSED:
+ assert bytesProduced == 0;
+ server.onDecodeInboundClosed(traceId);
+ server.decoder = TlsState.initialClosed(server.state) ? decodeIgnoreAll : decodeHandshake;
progress += bytesConsumed;
+ break;
}
- else
- {
- assert bytesConsumed == tlsRecordBytes;
- assert bytesProduced <= bytesConsumed : String.format("%d <= %d", bytesProduced, bytesConsumed);
-
- tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit)
- .payload(outAppBuffer, 0, bytesProduced)
- .build();
-
- server.decodableRecordBytes -= bytesConsumed;
- assert server.decodableRecordBytes == 0;
-
- server.decoder = decodeNotHandshakingUnwrapped;
- }
- break;
- case CLOSED:
- assert bytesProduced == 0;
- server.onDecodeInboundClosed(traceId);
- server.decoder = TlsState.initialClosed(server.state) ? decodeIgnoreAll : decodeHandshake;
- progress += bytesConsumed;
- break;
+ }
+ catch (SSLProtocolException ex)
+ {
+ event.tlsProtocolRejected(traceId, server.routedId);
+ throw ex;
+ }
+ catch (SSLKeyException ex)
+ {
+ event.tlsKeyRejected(traceId, server.routedId);
+ throw ex;
+ }
+ catch (SSLPeerUnverifiedException ex)
+ {
+ event.tlsPeerNotVerified(traceId, server.routedId);
+ throw ex;
+ }
+ catch (SSLHandshakeException ex)
+ {
+ event.tlsHandshakeFailed(traceId, server.routedId);
+ throw ex;
+ }
+ catch (SSLException | RuntimeException ex)
+ {
+ event.tlsFailed(traceId, server.routedId);
+ throw ex;
}
}
catch (SSLException | RuntimeException ex)
@@ -779,55 +842,83 @@ private int decodeHandshakeNeedUnwrap(
try
{
- final SSLEngineResult result = server.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer);
- final int bytesConsumed = result.bytesConsumed();
- final int bytesProduced = result.bytesProduced();
-
- switch (result.getStatus())
+ try
{
- case BUFFER_UNDERFLOW:
- if (TlsState.initialClosed(server.state))
- {
- server.decoder = decodeIgnoreAll;
- }
- break;
- case BUFFER_OVERFLOW:
- assert false;
- break;
- case OK:
- final HandshakeStatus handshakeStatus = result.getHandshakeStatus();
- if (handshakeStatus == HandshakeStatus.FINISHED)
- {
- server.onDecodeHandshakeFinished(traceId, budgetId);
- }
+ final SSLEngineResult result = server.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer);
+ final int bytesConsumed = result.bytesConsumed();
+ final int bytesProduced = result.bytesProduced();
- if (bytesProduced > 0 && handshakeStatus == HandshakeStatus.FINISHED)
- {
- final TlsRecordInfoFW tlsRecordInfo = tlsRecordInfoRW
- .wrap(buffer, progress, progress + bytesConsumed)
- .build();
- final int tlsRecordDataOffset = tlsRecordInfo.limit();
- final int tlsRecordDataLimit = tlsRecordDataOffset + tlsRecordInfo.length();
-
- tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit)
- .payload(outAppBuffer, 0, bytesProduced)
- .build();
- server.decoder = decodeNotHandshakingUnwrapped;
- }
- else
+ switch (result.getStatus())
{
- server.decoder = decodeHandshake;
+ case BUFFER_UNDERFLOW:
+ if (TlsState.initialClosed(server.state))
+ {
+ server.decoder = decodeIgnoreAll;
+ }
+ break;
+ case BUFFER_OVERFLOW:
+ assert false;
+ break;
+ case OK:
+ final HandshakeStatus handshakeStatus = result.getHandshakeStatus();
+ if (handshakeStatus == HandshakeStatus.FINISHED)
+ {
+ server.onDecodeHandshakeFinished(traceId, budgetId);
+ }
+
+ if (bytesProduced > 0 && handshakeStatus == HandshakeStatus.FINISHED)
+ {
+ final TlsRecordInfoFW tlsRecordInfo = tlsRecordInfoRW
+ .wrap(buffer, progress, progress + bytesConsumed)
+ .build();
+ final int tlsRecordDataOffset = tlsRecordInfo.limit();
+ final int tlsRecordDataLimit = tlsRecordDataOffset + tlsRecordInfo.length();
+
+ tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit)
+ .payload(outAppBuffer, 0, bytesProduced)
+ .build();
+ server.decoder = decodeNotHandshakingUnwrapped;
+ }
+ else
+ {
+ server.decoder = decodeHandshake;
+ }
+
+ break;
+ case CLOSED:
+ assert bytesProduced == 0;
+ server.onDecodeInboundClosed(traceId);
+ server.decoder = decodeIgnoreAll;
+ break;
}
- break;
- case CLOSED:
- assert bytesProduced == 0;
- server.onDecodeInboundClosed(traceId);
- server.decoder = decodeIgnoreAll;
- break;
+ progress += bytesConsumed;
+ }
+ catch (SSLProtocolException ex)
+ {
+ event.tlsProtocolRejected(traceId, server.routedId);
+ throw ex;
+ }
+ catch (SSLKeyException ex)
+ {
+ event.tlsKeyRejected(traceId, server.routedId);
+ throw ex;
+ }
+ catch (SSLPeerUnverifiedException ex)
+ {
+ event.tlsPeerNotVerified(traceId, server.routedId);
+ throw ex;
+ }
+ catch (SSLHandshakeException ex)
+ {
+ event.tlsHandshakeFailed(traceId, server.routedId);
+ throw ex;
+ }
+ catch (SSLException | RuntimeException ex)
+ {
+ event.tlsFailed(traceId, server.routedId);
+ throw ex;
}
-
- progress += bytesConsumed;
}
catch (SSLException | RuntimeException ex)
{
@@ -1309,6 +1400,7 @@ private void onNetSignalHandshakeTimeout(
handshakeTimeoutFutureId = NO_CANCEL_ID;
cleanupNet(traceId);
+ event.tlsHandshakeFailed(traceId, routedId);
decoder = decodeIgnoreAll;
}
}
@@ -1652,36 +1744,64 @@ private void doEncodeWrap(
try
{
- loop:
- do
+ try
{
- final SSLEngineResult result = tlsEngine.wrap(inAppByteBuffer, outNetByteBuffer);
- final int bytesProduced = result.bytesProduced();
-
- switch (result.getStatus())
+ loop:
+ do
{
- case BUFFER_OVERFLOW:
- case BUFFER_UNDERFLOW:
- assert false;
- break;
- case CLOSED:
- assert bytesProduced > 0;
- assert tlsEngine.isOutboundDone();
- stream.ifPresent(s -> s.doAppResetLater(traceId));
- state = TlsState.closingReply(state);
- break loop;
- case OK:
- assert bytesProduced > 0 || tlsEngine.isInboundDone();
- if (result.getHandshakeStatus() == HandshakeStatus.FINISHED)
+ final SSLEngineResult result = tlsEngine.wrap(inAppByteBuffer, outNetByteBuffer);
+ final int bytesProduced = result.bytesProduced();
+
+ switch (result.getStatus())
{
- onDecodeHandshakeFinished(traceId, budgetId);
+ case BUFFER_OVERFLOW:
+ case BUFFER_UNDERFLOW:
+ assert false;
+ break;
+ case CLOSED:
+ assert bytesProduced > 0;
+ assert tlsEngine.isOutboundDone();
+ stream.ifPresent(s -> s.doAppResetLater(traceId));
+ state = TlsState.closingReply(state);
+ break loop;
+ case OK:
+ assert bytesProduced > 0 || tlsEngine.isInboundDone();
+ if (result.getHandshakeStatus() == HandshakeStatus.FINISHED)
+ {
+ onDecodeHandshakeFinished(traceId, budgetId);
+ }
+ break;
}
- break;
- }
- } while (inAppByteBuffer.hasRemaining());
+ } while (inAppByteBuffer.hasRemaining());
- final int outNetBytesProduced = outNetByteBuffer.position();
- doNetData(traceId, budgetId, outNetBuffer, 0, outNetBytesProduced);
+ final int outNetBytesProduced = outNetByteBuffer.position();
+ doNetData(traceId, budgetId, outNetBuffer, 0, outNetBytesProduced);
+ }
+ catch (SSLProtocolException ex)
+ {
+ event.tlsProtocolRejected(traceId, routedId);
+ throw ex;
+ }
+ catch (SSLKeyException ex)
+ {
+ event.tlsKeyRejected(traceId, routedId);
+ throw ex;
+ }
+ catch (SSLPeerUnverifiedException ex)
+ {
+ event.tlsPeerNotVerified(traceId, routedId);
+ throw ex;
+ }
+ catch (SSLHandshakeException ex)
+ {
+ event.tlsHandshakeFailed(traceId, routedId);
+ throw ex;
+ }
+ catch (SSLException | RuntimeException ex)
+ {
+ event.tlsFailed(traceId, routedId);
+ throw ex;
+ }
}
catch (SSLException | RuntimeException ex)
{
diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/Engine.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/Engine.java
index 7a8f6935a8..d6d2bda6ea 100644
--- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/Engine.java
+++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/Engine.java
@@ -57,6 +57,8 @@
import org.agrona.concurrent.AgentRunner;
import io.aklivity.zilla.runtime.engine.binding.Binding;
+import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer;
+import io.aklivity.zilla.runtime.engine.binding.function.MessageReader;
import io.aklivity.zilla.runtime.engine.catalog.Catalog;
import io.aklivity.zilla.runtime.engine.config.KindConfig;
import io.aklivity.zilla.runtime.engine.exporter.Exporter;
@@ -71,6 +73,7 @@
import io.aklivity.zilla.runtime.engine.internal.registry.FileWatcherTask;
import io.aklivity.zilla.runtime.engine.internal.registry.HttpWatcherTask;
import io.aklivity.zilla.runtime.engine.internal.registry.WatcherTask;
+import io.aklivity.zilla.runtime.engine.internal.types.event.EventFW;
import io.aklivity.zilla.runtime.engine.metrics.Collector;
import io.aklivity.zilla.runtime.engine.metrics.MetricGroup;
import io.aklivity.zilla.runtime.engine.model.Model;
@@ -160,7 +163,7 @@ public final class Engine implements Collector, AutoCloseable
EngineWorker worker =
new EngineWorker(config, tasks, labels, errorHandler, tuning::affinity,
bindings, exporters, guards, vaults, catalogs, models, metricGroups,
- this, coreIndex, readonly);
+ this, this::supplyEventReader, coreIndex, readonly);
workers.add(worker);
}
this.workers = workers;
@@ -481,6 +484,11 @@ public long[][] histogramIds()
return worker.histogramIds();
}
+ public MessageReader supplyEventReader()
+ {
+ return new EventReader();
+ }
+
public String supplyLocalName(
long namespacedId)
{
@@ -495,6 +503,48 @@ public int supplyLabelId(
return worker.supplyTypeId(label);
}
+ private final class EventReader implements MessageReader
+ {
+ private final EventFW eventRO = new EventFW();
+ private int minWorkerIndex;
+ private long minTimeStamp;
+
+ @Override
+ public int read(
+ MessageConsumer handler,
+ int messageCountLimit)
+ {
+ int messagesRead = 0;
+ boolean empty = false;
+ while (!empty && messagesRead < messageCountLimit)
+ {
+ int eventCount = 0;
+ minWorkerIndex = 0;
+ minTimeStamp = Long.MAX_VALUE;
+ for (int j = 0; j < workers.size(); j++)
+ {
+ final int workerIndex = j;
+ int eventPeeked = workers.get(workerIndex).peekEvent((m, b, i, l) ->
+ {
+ eventRO.wrap(b, i, i + l);
+ if (eventRO.timestamp() < minTimeStamp)
+ {
+ minTimeStamp = eventRO.timestamp();
+ minWorkerIndex = workerIndex;
+ }
+ });
+ eventCount += eventPeeked;
+ }
+ empty = eventCount == 0;
+ if (!empty)
+ {
+ messagesRead += workers.get(minWorkerIndex).readEvent(handler, 1);
+ }
+ }
+ return messagesRead;
+ }
+ }
+
// visible for testing
public final class ContextImpl implements EngineExtContext
{
diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineConfiguration.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineConfiguration.java
index 8ef1e2723a..4cd79e9eed 100644
--- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineConfiguration.java
+++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineConfiguration.java
@@ -52,6 +52,7 @@ public class EngineConfiguration extends Configuration
public static final IntPropertyDef ENGINE_BUFFER_POOL_CAPACITY;
public static final IntPropertyDef ENGINE_BUFFER_SLOT_CAPACITY;
public static final IntPropertyDef ENGINE_STREAMS_BUFFER_CAPACITY;
+ public static final IntPropertyDef ENGINE_EVENTS_BUFFER_CAPACITY;
public static final IntPropertyDef ENGINE_COUNTERS_BUFFER_CAPACITY;
public static final IntPropertyDef ENGINE_BUDGETS_BUFFER_CAPACITY;
public static final BooleanPropertyDef ENGINE_TIMESTAMPS;
@@ -88,6 +89,8 @@ public class EngineConfiguration extends Configuration
ENGINE_BUFFER_SLOT_CAPACITY = config.property("buffer.slot.capacity", 64 * 1024);
ENGINE_STREAMS_BUFFER_CAPACITY = config.property("streams.buffer.capacity",
EngineConfiguration::defaultStreamsBufferCapacity);
+ ENGINE_EVENTS_BUFFER_CAPACITY = config.property("events.buffer.capacity",
+ EngineConfiguration::defaultEventsBufferCapacity);
ENGINE_BUDGETS_BUFFER_CAPACITY = config.property("budgets.buffer.capacity",
EngineConfiguration::defaultBudgetsBufferCapacity);
ENGINE_COUNTERS_BUFFER_CAPACITY = config.property("counters.buffer.capacity", 1024 * 1024);
@@ -179,6 +182,11 @@ public int streamsBufferCapacity()
return ENGINE_STREAMS_BUFFER_CAPACITY.getAsInt(this);
}
+ public int eventsBufferCapacity()
+ {
+ return ENGINE_EVENTS_BUFFER_CAPACITY.getAsInt(this);
+ }
+
public int countersBufferCapacity()
{
return ENGINE_COUNTERS_BUFFER_CAPACITY.getAsInt(this);
@@ -276,6 +284,12 @@ private static int defaultStreamsBufferCapacity(
return ENGINE_BUFFER_SLOT_CAPACITY.get(config) * ENGINE_WORKER_CAPACITY.getAsInt(config);
}
+ private static int defaultEventsBufferCapacity(
+ Configuration config)
+ {
+ return ENGINE_BUFFER_SLOT_CAPACITY.get(config) * ENGINE_WORKER_CAPACITY.getAsInt(config);
+ }
+
private static int defaultBudgetsBufferCapacity(
Configuration config)
{
diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineContext.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineContext.java
index 2f113352a8..0360890834 100644
--- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineContext.java
+++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineContext.java
@@ -18,12 +18,14 @@
import java.net.InetAddress;
import java.net.URL;
import java.nio.channels.SelectableChannel;
+import java.time.Clock;
import java.util.function.LongSupplier;
import org.agrona.MutableDirectBuffer;
import io.aklivity.zilla.runtime.engine.binding.BindingHandler;
import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer;
+import io.aklivity.zilla.runtime.engine.binding.function.MessageReader;
import io.aklivity.zilla.runtime.engine.budget.BudgetCreditor;
import io.aklivity.zilla.runtime.engine.budget.BudgetDebitor;
import io.aklivity.zilla.runtime.engine.buffer.BufferPool;
@@ -117,6 +119,9 @@ String supplyNamespace(
String supplyLocalName(
long namespacedId);
+ String supplyQName(
+ long namespacedId);
+
BindingHandler streamFactory();
GuardHandler supplyGuard(
@@ -148,4 +153,10 @@ void onExporterAttached(
void onExporterDetached(
long exporterId);
+
+ MessageConsumer supplyEventWriter();
+
+ MessageReader supplyEventReader();
+
+ Clock clock();
}
diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/binding/function/MessageReader.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/binding/function/MessageReader.java
new file mode 100644
index 0000000000..9759b940d5
--- /dev/null
+++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/binding/function/MessageReader.java
@@ -0,0 +1,23 @@
+/*
+ * 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.engine.binding.function;
+
+public interface MessageReader
+{
+ int read(
+ MessageConsumer handler,
+ int messageCountLimit);
+}
diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/guard/GuardHandler.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/guard/GuardHandler.java
index 54c3f372fc..a9da2772fe 100644
--- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/guard/GuardHandler.java
+++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/guard/GuardHandler.java
@@ -30,6 +30,8 @@ public interface GuardHandler
* @return the session identifier
*/
long reauthorize(
+ long traceId,
+ long bindingId,
long contextId,
String credentials);
diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/layouts/EventsLayout.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/layouts/EventsLayout.java
new file mode 100644
index 0000000000..ee3670f16b
--- /dev/null
+++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/layouts/EventsLayout.java
@@ -0,0 +1,173 @@
+/*
+ * 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.engine.internal.layouts;
+
+import static io.aklivity.zilla.runtime.engine.internal.spy.RingBufferSpy.SpyPosition.ZERO;
+import static org.agrona.IoUtil.createEmptyFile;
+import static org.agrona.IoUtil.mapExistingFile;
+import static org.agrona.IoUtil.unmap;
+import static org.agrona.LangUtil.rethrowUnchecked;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.MappedByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.agrona.CloseHelper;
+import org.agrona.DirectBuffer;
+import org.agrona.concurrent.AtomicBuffer;
+import org.agrona.concurrent.UnsafeBuffer;
+import org.agrona.concurrent.ringbuffer.OneToOneRingBuffer;
+import org.agrona.concurrent.ringbuffer.RingBuffer;
+import org.agrona.concurrent.ringbuffer.RingBufferDescriptor;
+
+import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer;
+import io.aklivity.zilla.runtime.engine.internal.spy.OneToOneRingBufferSpy;
+import io.aklivity.zilla.runtime.engine.internal.spy.RingBufferSpy;
+
+public final class EventsLayout implements AutoCloseable
+{
+ private final Path path;
+ private final long capacity;
+
+ private RingBuffer buffer;
+ private RingBufferSpy bufferSpy;
+
+ private EventsLayout(
+ Path path,
+ long capacity,
+ RingBuffer buffer,
+ RingBufferSpy bufferSpy)
+ {
+ this.path = path;
+ this.capacity = capacity;
+ this.buffer = buffer;
+ this.bufferSpy = bufferSpy;
+ }
+
+ @Override
+ public void close()
+ {
+ unmap(buffer.buffer().byteBuffer());
+ }
+
+ public void writeEvent(
+ int msgTypeId,
+ DirectBuffer recordBuffer,
+ int index,
+ int length)
+ {
+ boolean success = buffer.write(msgTypeId, recordBuffer, index, length);
+ if (!success)
+ {
+ rotateFile();
+ buffer.write(msgTypeId, recordBuffer, index, length);
+ }
+ }
+
+ public int readEvent(
+ MessageConsumer handler,
+ int messageCountLimit)
+ {
+ return bufferSpy.spy(handler, messageCountLimit);
+ }
+
+ public int peekEvent(
+ MessageConsumer handler)
+ {
+ return bufferSpy.peek(handler);
+ }
+
+ private void rotateFile()
+ {
+ close();
+ String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
+ Path newPath = Path.of(String.format("%s_%s", path, timestamp));
+ try
+ {
+ Files.move(path, newPath, StandardCopyOption.REPLACE_EXISTING);
+ }
+ catch (IOException ex)
+ {
+ ex.printStackTrace();
+ rethrowUnchecked(ex);
+ }
+ buffer = createRingBuffer(path, capacity);
+ bufferSpy = createRingBufferSpy(path);
+ }
+
+ private static AtomicBuffer createAtomicBuffer(
+ Path path,
+ long capacity,
+ boolean createFile)
+ {
+ final File layoutFile = path.toFile();
+ if (createFile)
+ {
+ CloseHelper.close(createEmptyFile(layoutFile, capacity + RingBufferDescriptor.TRAILER_LENGTH));
+ }
+ final MappedByteBuffer mappedBuffer = mapExistingFile(layoutFile, "events");
+ return new UnsafeBuffer(mappedBuffer);
+ }
+
+ private static RingBuffer createRingBuffer(
+ Path path,
+ long capacity)
+ {
+ AtomicBuffer atomicBuffer = createAtomicBuffer(path, capacity, true);
+ return new OneToOneRingBuffer(atomicBuffer);
+ }
+
+ private static RingBufferSpy createRingBufferSpy(
+ Path path)
+ {
+ AtomicBuffer atomicBuffer = createAtomicBuffer(path, 0, false);
+ OneToOneRingBufferSpy spy = new OneToOneRingBufferSpy(atomicBuffer);
+ spy.spyAt(ZERO);
+ return spy;
+ }
+
+ public static final class Builder
+ {
+ private long capacity;
+ private Path path;
+
+ public Builder capacity(
+ long capacity)
+ {
+ this.capacity = capacity;
+ return this;
+ }
+
+ public Builder path(
+ Path path)
+ {
+ this.path = path;
+ return this;
+ }
+
+ public EventsLayout build()
+ {
+ RingBuffer ringBuffer = createRingBuffer(path, capacity);
+ RingBufferSpy ringBufferSpy = createRingBufferSpy(path);
+ return new EventsLayout(path, capacity, ringBuffer, ringBufferSpy);
+ }
+ }
+}
diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineWorker.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineWorker.java
index 63be6936fb..f07bfe1a2f 100644
--- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineWorker.java
+++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineWorker.java
@@ -42,6 +42,7 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.channels.SelectableChannel;
+import java.time.Clock;
import java.time.Duration;
import java.util.BitSet;
import java.util.Collection;
@@ -60,6 +61,7 @@
import java.util.function.LongFunction;
import java.util.function.LongSupplier;
import java.util.function.LongUnaryOperator;
+import java.util.function.Supplier;
import org.agrona.DeadlineTimerWheel;
import org.agrona.DeadlineTimerWheel.TimerHandler;
@@ -86,6 +88,7 @@
import io.aklivity.zilla.runtime.engine.binding.BindingContext;
import io.aklivity.zilla.runtime.engine.binding.BindingHandler;
import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer;
+import io.aklivity.zilla.runtime.engine.binding.function.MessageReader;
import io.aklivity.zilla.runtime.engine.budget.BudgetCreditor;
import io.aklivity.zilla.runtime.engine.budget.BudgetDebitor;
import io.aklivity.zilla.runtime.engine.buffer.BufferPool;
@@ -108,6 +111,7 @@
import io.aklivity.zilla.runtime.engine.internal.exporter.ExporterAgent;
import io.aklivity.zilla.runtime.engine.internal.layouts.BudgetsLayout;
import io.aklivity.zilla.runtime.engine.internal.layouts.BufferPoolLayout;
+import io.aklivity.zilla.runtime.engine.internal.layouts.EventsLayout;
import io.aklivity.zilla.runtime.engine.internal.layouts.StreamsLayout;
import io.aklivity.zilla.runtime.engine.internal.layouts.metrics.HistogramsLayout;
import io.aklivity.zilla.runtime.engine.internal.layouts.metrics.ScalarsLayout;
@@ -210,6 +214,8 @@ public class EngineWorker implements EngineContext, Agent
private final ScalarsLayout countersLayout;
private final ScalarsLayout gaugesLayout;
private final HistogramsLayout histogramsLayout;
+ private final EventsLayout eventsLayout;
+ private final Supplier supplyEventReader;
private long initialId;
private long promiseId;
private long traceId;
@@ -232,6 +238,7 @@ public EngineWorker(
Collection models,
Collection metricGroups,
Collector collector,
+ Supplier supplyEventReader,
int index,
boolean readonly)
{
@@ -285,6 +292,11 @@ public EngineWorker(
.readonly(readonly)
.build();
+ this.eventsLayout = new EventsLayout.Builder()
+ .path(config.directory().resolve(String.format("events%d", index)))
+ .capacity(config.eventsBufferCapacity())
+ .build();
+
this.agentName = String.format("engine/data#%d", index);
this.streamsLayout = streamsLayout;
this.bufferPoolLayout = bufferPoolLayout;
@@ -407,6 +419,7 @@ public EngineWorker(
this.idleStrategy = idleStrategy;
this.errorHandler = errorHandler;
this.exportersById = new Long2ObjectHashMap<>();
+ this.supplyEventReader = supplyEventReader;
}
public static int indexOfId(
@@ -441,6 +454,14 @@ public String supplyLocalName(
return labels.lookupLabel(NamespacedId.localId(namespacedId));
}
+ @Override
+ public String supplyQName(
+ long namespacedId)
+ {
+ return String.format("%s.%s", labels.lookupLabel(NamespacedId.namespaceId(namespacedId)),
+ labels.lookupLabel(NamespacedId.localId(namespacedId)));
+ }
+
@Override
public int supplyTypeId(
String name)
@@ -895,6 +916,18 @@ public LongConsumer supplyHistogramWriter(
return histogramsLayout.supplyWriter(bindingId, metricId);
}
+ @Override
+ public MessageConsumer supplyEventWriter()
+ {
+ return this.eventsLayout::writeEvent;
+ }
+
+ @Override
+ public Clock clock()
+ {
+ return Clock.systemUTC();
+ }
+
private void onSystemMessage(
int msgTypeId,
DirectBuffer buffer,
@@ -1557,6 +1590,24 @@ public MessageConsumer supplyReceiver(
return writersByIndex.computeIfAbsent(remoteIndex, supplyWriter);
}
+ public int readEvent(
+ MessageConsumer handler,
+ int messageCountLimit)
+ {
+ return eventsLayout.readEvent(handler, messageCountLimit);
+ }
+
+ public int peekEvent(
+ MessageConsumer handler)
+ {
+ return eventsLayout.peekEvent(handler);
+ }
+
+ public MessageReader supplyEventReader()
+ {
+ return supplyEventReader.get();
+ }
+
private MessageConsumer supplyWriter(
int index)
{
diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/OneToOneRingBufferSpy.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/OneToOneRingBufferSpy.java
new file mode 100644
index 0000000000..4104b505ff
--- /dev/null
+++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/OneToOneRingBufferSpy.java
@@ -0,0 +1,167 @@
+/*
+ * 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.engine.internal.spy;
+
+import static org.agrona.BitUtil.align;
+import static org.agrona.concurrent.ringbuffer.RecordDescriptor.ALIGNMENT;
+import static org.agrona.concurrent.ringbuffer.RecordDescriptor.HEADER_LENGTH;
+import static org.agrona.concurrent.ringbuffer.RecordDescriptor.lengthOffset;
+import static org.agrona.concurrent.ringbuffer.RecordDescriptor.typeOffset;
+import static org.agrona.concurrent.ringbuffer.RingBuffer.PADDING_MSG_TYPE_ID;
+import static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.HEAD_POSITION_OFFSET;
+import static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.TAIL_POSITION_OFFSET;
+import static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.TRAILER_LENGTH;
+import static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.checkCapacity;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.agrona.DirectBuffer;
+import org.agrona.concurrent.AtomicBuffer;
+
+import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer;
+
+public class OneToOneRingBufferSpy implements RingBufferSpy
+{
+ private final int capacity;
+ private final AtomicLong spyPosition;
+ private final AtomicBuffer buffer;
+
+ public OneToOneRingBufferSpy(
+ final AtomicBuffer buffer)
+ {
+ this.buffer = buffer;
+ checkCapacity(buffer.capacity());
+ capacity = buffer.capacity() - TRAILER_LENGTH;
+
+ buffer.verifyAlignment();
+
+ spyPosition = new AtomicLong();
+ }
+
+ @Override
+ public void spyAt(
+ SpyPosition position)
+ {
+ switch (position)
+ {
+ case ZERO:
+ spyPosition.lazySet(0);
+ break;
+ case HEAD:
+ spyPosition.lazySet(buffer.getLong(capacity + HEAD_POSITION_OFFSET));
+ break;
+ case TAIL:
+ spyPosition.lazySet(buffer.getLong(capacity + TAIL_POSITION_OFFSET));
+ break;
+ }
+ }
+
+ @Override
+ public DirectBuffer buffer()
+ {
+ return buffer;
+ }
+
+ @Override
+ public long producerPosition()
+ {
+ return buffer.getLong(buffer.capacity() - TRAILER_LENGTH + TAIL_POSITION_OFFSET);
+ }
+
+ @Override
+ public long consumerPosition()
+ {
+ return buffer.getLong(buffer.capacity() - TRAILER_LENGTH + HEAD_POSITION_OFFSET);
+ }
+
+ @Override
+ public int spy(
+ final MessageConsumer handler)
+ {
+ return spy(handler, Integer.MAX_VALUE);
+ }
+
+ @Override
+ public int spy(
+ final MessageConsumer handler,
+ final int messageCountLimit)
+ {
+ int messagesRead = 0;
+
+ final AtomicBuffer buffer = this.buffer;
+ final long head = spyPosition.get();
+
+ int bytesRead = 0;
+
+ final int capacity = this.capacity;
+ final int headIndex = (int)head & (capacity - 1);
+ final int contiguousBlockLength = capacity - headIndex;
+
+ try
+ {
+ while (bytesRead < contiguousBlockLength && messagesRead < messageCountLimit)
+ {
+ final int recordIndex = headIndex + bytesRead;
+ final int recordLength = buffer.getIntVolatile(lengthOffset(recordIndex));
+ if (recordLength <= 0)
+ {
+ break;
+ }
+
+ bytesRead += align(recordLength, ALIGNMENT);
+
+ final int messageTypeId = buffer.getInt(typeOffset(recordIndex));
+ if (PADDING_MSG_TYPE_ID == messageTypeId)
+ {
+ continue;
+ }
+
+ ++messagesRead;
+ handler.accept(messageTypeId, buffer, recordIndex + HEADER_LENGTH, recordLength - HEADER_LENGTH);
+ }
+ }
+ finally
+ {
+ if (bytesRead != 0)
+ {
+ spyPosition.lazySet(head + bytesRead);
+ }
+ }
+
+ return messagesRead;
+ }
+
+ public int peek(
+ final MessageConsumer handler)
+ {
+ final AtomicBuffer buffer = this.buffer;
+ final long head = spyPosition.get();
+ final int capacity = this.capacity;
+ final int headIndex = (int)head & (capacity - 1);
+ final int recordLength = buffer.getIntVolatile(lengthOffset(headIndex));
+ int messagesPeeked = 0;
+ if (recordLength > 0)
+ {
+ final int messageTypeId = buffer.getInt(typeOffset(headIndex));
+ if (PADDING_MSG_TYPE_ID != messageTypeId)
+ {
+ handler.accept(messageTypeId, buffer, headIndex + HEADER_LENGTH, recordLength - HEADER_LENGTH);
+ messagesPeeked = 1;
+ }
+ }
+ return messagesPeeked;
+ }
+}
diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/RingBufferSpy.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/RingBufferSpy.java
new file mode 100644
index 0000000000..a863b76d1e
--- /dev/null
+++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/RingBufferSpy.java
@@ -0,0 +1,41 @@
+/*
+ * 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.engine.internal.spy;
+
+import org.agrona.DirectBuffer;
+
+import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer;
+
+public interface RingBufferSpy
+{
+ enum SpyPosition
+ {
+ ZERO,
+ HEAD,
+ TAIL
+ }
+
+ void spyAt(SpyPosition position);
+
+ int spy(MessageConsumer handler);
+ int spy(MessageConsumer handler, int messageCountLimit);
+ int peek(MessageConsumer handler);
+
+ long producerPosition();
+ long consumerPosition();
+
+ DirectBuffer buffer();
+}
diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/EngineTest.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/EngineTest.java
index 711dca56e2..4ae65aba4e 100644
--- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/EngineTest.java
+++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/EngineTest.java
@@ -34,6 +34,7 @@
import io.aklivity.zilla.runtime.engine.Engine;
import io.aklivity.zilla.runtime.engine.EngineConfiguration;
+import io.aklivity.zilla.runtime.engine.binding.function.MessageReader;
import io.aklivity.zilla.runtime.engine.ext.EngineExtContext;
import io.aklivity.zilla.runtime.engine.ext.EngineExtSpi;
@@ -250,6 +251,30 @@ public void shouldNotConfigureUnknownScheme() throws Exception
}
}
+ @Test
+ public void shouldReadEvents()
+ {
+ List errors = new LinkedList<>();
+ EngineConfiguration config = new EngineConfiguration(properties);
+ try (Engine engine = Engine.builder()
+ .config(config)
+ .errorHandler(errors::add)
+ .build())
+ {
+ engine.start();
+ MessageReader events = engine.supplyEventReader();
+ events.read((m, b, i, l) -> {}, 1);
+ }
+ catch (Throwable ex)
+ {
+ errors.add(ex);
+ }
+ finally
+ {
+ assertThat(errors, empty());
+ }
+ }
+
public static final class TestEngineExt implements EngineExtSpi
{
public static volatile CountDownLatch registerLatch = new CountDownLatch(1);
diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/EventsLayoutTest.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/EventsLayoutTest.java
new file mode 100644
index 0000000000..a0aeda974b
--- /dev/null
+++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/EventsLayoutTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.engine.internal.layouts;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.agrona.DirectBuffer;
+import org.agrona.concurrent.UnsafeBuffer;
+import org.junit.Test;
+
+public class EventsLayoutTest
+{
+ private static final Path PATH = Paths.get("target/zilla-itests/events0");
+ private static final int CAPACITY = 1024;
+
+ private int msgTypeId;
+
+ @Test
+ public void shouldWriteAndReadEvents()
+ {
+ // GIVEN
+ EventsLayout layout = new EventsLayout.Builder()
+ .path(PATH)
+ .capacity(CAPACITY)
+ .build();
+ layout.writeEvent(42, new UnsafeBuffer(), 0, 0);
+ msgTypeId = 0;
+
+ // WHEN
+ int count = layout.readEvent(this::readEvent, 1);
+
+ // THEN
+ assertThat(count, equalTo(1));
+ assertThat(msgTypeId, equalTo(42));
+ }
+
+ private void readEvent(
+ int msgTypeId,
+ DirectBuffer buffer,
+ int index,
+ int length)
+ {
+ this.msgTypeId = msgTypeId;
+ }
+}
diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/HistogramsLayoutTest.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/HistogramsLayoutTest.java
similarity index 98%
rename from runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/HistogramsLayoutTest.java
rename to runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/HistogramsLayoutTest.java
index 9109ab83c2..9d5d5070ae 100644
--- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/HistogramsLayoutTest.java
+++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/HistogramsLayoutTest.java
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
-package io.aklivity.zilla.runtime.engine.internal.layout.metrics;
+package io.aklivity.zilla.runtime.engine.internal.layouts.metrics;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
@@ -28,8 +28,6 @@
import org.junit.Test;
-import io.aklivity.zilla.runtime.engine.internal.layouts.metrics.HistogramsLayout;
-
public class HistogramsLayoutTest
{
@Test
diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/ScalarsLayoutTest.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/ScalarsLayoutTest.java
similarity index 96%
rename from runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/ScalarsLayoutTest.java
rename to runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/ScalarsLayoutTest.java
index bbfdefa615..0a31ac6eb1 100644
--- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/ScalarsLayoutTest.java
+++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/ScalarsLayoutTest.java
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
-package io.aklivity.zilla.runtime.engine.internal.layout.metrics;
+package io.aklivity.zilla.runtime.engine.internal.layouts.metrics;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
@@ -28,8 +28,6 @@
import org.junit.Test;
-import io.aklivity.zilla.runtime.engine.internal.layouts.metrics.ScalarsLayout;
-
public class ScalarsLayoutTest
{
@Test
diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/guard/TestGuardHandler.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/guard/TestGuardHandler.java
index 1e872dfcdd..ccc663209e 100644
--- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/guard/TestGuardHandler.java
+++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/guard/TestGuardHandler.java
@@ -50,6 +50,8 @@ public TestGuardHandler(
@Override
public long reauthorize(
+ long traceId,
+ long bindingId,
long contextId,
String credentials)
{
diff --git a/runtime/guard-jwt/pom.xml b/runtime/guard-jwt/pom.xml
index 143b7c3182..fa3544628a 100644
--- a/runtime/guard-jwt/pom.xml
+++ b/runtime/guard-jwt/pom.xml
@@ -103,6 +103,22 @@
org.jasig.maven
maven-notice-plugin
+
+ ${project.groupId}
+ flyweight-maven-plugin
+ ${project.version}
+
+ core jwt
+ io.aklivity.zilla.runtime.guard.jwt.internal.types
+
+
+
+
+ generate
+
+
+
+
com.mycila
license-maven-plugin
@@ -185,6 +201,9 @@
org.jacoco
jacoco-maven-plugin
+
+ io/aklivity/zilla/runtime/guard/jwt/internal/types/**/*.class
+
BUNDLE
diff --git a/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtEventContext.java b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtEventContext.java
new file mode 100644
index 0000000000..99d0fa6146
--- /dev/null
+++ b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtEventContext.java
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+package io.aklivity.zilla.runtime.guard.jwt.internal;
+
+import java.nio.ByteBuffer;
+import java.time.Clock;
+
+import org.agrona.MutableDirectBuffer;
+import org.agrona.concurrent.UnsafeBuffer;
+
+import io.aklivity.zilla.runtime.engine.EngineContext;
+import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer;
+import io.aklivity.zilla.runtime.guard.jwt.internal.types.event.JwtEventFW;
+
+public class JwtEventContext
+{
+ private static final int EVENT_BUFFER_CAPACITY = 1024;
+
+ private final JwtEventFW.Builder jwtEventRW = new JwtEventFW.Builder();
+ private final MutableDirectBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY));
+ private final int jwtTypeId;
+ private final MessageConsumer eventWriter;
+ private final Clock clock;
+
+ public JwtEventContext(
+ EngineContext context)
+ {
+ this.jwtTypeId = context.supplyTypeId(JwtGuard.NAME);
+ this.eventWriter = context.supplyEventWriter();
+ this.clock = context.clock();
+ }
+
+ public void authorizationFailed(
+ long traceId,
+ long bindingId,
+ String identity)
+ {
+ JwtEventFW event = jwtEventRW
+ .wrap(eventBuffer, 0, eventBuffer.capacity())
+ .authorizationFailed(e -> e
+ .timestamp(clock.millis())
+ .traceId(traceId)
+ .namespacedId(bindingId)
+ .identity(identity)
+ )
+ .build();
+ eventWriter.accept(jwtTypeId, event.buffer(), event.offset(), event.limit());
+ }
+}
diff --git a/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardContext.java b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardContext.java
index 1412213c25..55c141be78 100644
--- a/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardContext.java
+++ b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardContext.java
@@ -28,12 +28,14 @@ final class JwtGuardContext implements GuardContext
{
private final Long2ObjectHashMap handlersById;
private final LongSupplier supplyAuthorizedId;
+ private final EngineContext context;
JwtGuardContext(
Configuration config,
EngineContext context)
{
this.handlersById = new Long2ObjectHashMap<>();
+ this.context = context;
this.supplyAuthorizedId = context::supplyAuthorizedId;
}
@@ -42,7 +44,7 @@ public JwtGuardHandler attach(
GuardConfig guard)
{
JwtOptionsConfig options = (JwtOptionsConfig) guard.options;
- JwtGuardHandler handler = new JwtGuardHandler(options, supplyAuthorizedId, guard.readURL);
+ JwtGuardHandler handler = new JwtGuardHandler(options, context, supplyAuthorizedId, guard.readURL);
handlersById.put(guard.id, handler);
return handler;
}
diff --git a/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandler.java b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandler.java
index 54f2358905..b34a2edd36 100644
--- a/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandler.java
+++ b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandler.java
@@ -42,6 +42,7 @@
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.lang.JoseException;
+import io.aklivity.zilla.runtime.engine.EngineContext;
import io.aklivity.zilla.runtime.engine.guard.GuardHandler;
import io.aklivity.zilla.runtime.guard.jwt.config.JwtKeyConfig;
import io.aklivity.zilla.runtime.guard.jwt.config.JwtKeySetConfig;
@@ -59,9 +60,11 @@ public class JwtGuardHandler implements GuardHandler
private final Long2ObjectHashMap sessionsById;
private final LongSupplier supplyAuthorizedId;
private final Long2ObjectHashMap sessionStoresByContextId;
+ private final JwtEventContext event;
public JwtGuardHandler(
JwtOptionsConfig options,
+ EngineContext context,
LongSupplier supplyAuthorizedId,
Function readURL)
{
@@ -113,14 +116,18 @@ public JwtGuardHandler(
this.supplyAuthorizedId = supplyAuthorizedId;
this.sessionsById = new Long2ObjectHashMap<>();
this.sessionStoresByContextId = new Long2ObjectHashMap<>();
+ this.event = new JwtEventContext(context);
}
@Override
public long reauthorize(
+ long traceId,
+ long bindingId,
long contextId,
String credentials)
{
JwtSession session = null;
+ String subject = null;
authorize:
try
@@ -147,6 +154,7 @@ public long reauthorize(
String payload = signature.getPayload();
JwtClaims claims = JwtClaims.parse(payload);
+ subject = claims.getSubject();
NumericDate notBefore = claims.getNotBefore();
NumericDate notAfter = claims.getExpirationTime();
String issuer = claims.getIssuer();
@@ -161,7 +169,6 @@ public long reauthorize(
break authorize;
}
- String subject = claims.getSubject();
List roles = Optional.ofNullable(claims.getClaimValue("scope"))
.map(s -> s.toString().intern())
.map(s -> s.split("\\s+"))
@@ -185,7 +192,10 @@ public long reauthorize(
{
// not authorized
}
-
+ if (session == null)
+ {
+ event.authorizationFailed(traceId, bindingId, subject);
+ }
return session != null ? session.authorized : NOT_AUTHORIZED;
}
diff --git a/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandlerTest.java b/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandlerTest.java
index 88649c6a63..90bd4add79 100644
--- a/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandlerTest.java
+++ b/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandlerTest.java
@@ -24,8 +24,11 @@
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import java.security.KeyPair;
+import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.function.Function;
@@ -34,14 +37,27 @@
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.lang.JoseException;
+import org.junit.Before;
import org.junit.Test;
+import io.aklivity.zilla.runtime.engine.EngineContext;
+import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer;
import io.aklivity.zilla.runtime.guard.jwt.config.JwtOptionsConfig;
public class JwtGuardHandlerTest
{
private static final Function READ_KEYS_URL = url -> "{}";
+ private EngineContext context;
+
+ @Before
+ public void init()
+ {
+ context = mock(EngineContext.class);
+ when(context.clock()).thenReturn(mock(Clock.class));
+ when(context.supplyEventWriter()).thenReturn(mock(MessageConsumer.class));
+ }
+
@Test
public void shouldAuthorize() throws Exception
{
@@ -53,7 +69,7 @@ public void shouldAuthorize() throws Exception
.key(RFC7515_RS256_CONFIG)
.challenge(challenge)
.build();
- JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
+ JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
Instant now = Instant.now();
@@ -66,7 +82,7 @@ public void shouldAuthorize() throws Exception
String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionId = guard.reauthorize(101L, token);
+ long sessionId = guard.reauthorize(0L, 0L, 101L, token);
assertThat(sessionId, not(equalTo(0L)));
assertThat(guard.identity(sessionId), equalTo("testSubject"));
@@ -86,7 +102,7 @@ public void shouldChallengeDuringChallengeWindow() throws Exception
.key(RFC7515_RS256_CONFIG)
.challenge(challenge)
.build();
- JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
+ JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
Instant now = Instant.now();
@@ -99,7 +115,7 @@ public void shouldChallengeDuringChallengeWindow() throws Exception
String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionId = guard.reauthorize(101L, token);
+ long sessionId = guard.reauthorize(0L, 0L, 101L, token);
assertTrue(guard.challenge(sessionId, now.plusSeconds(8L).toEpochMilli()));
}
@@ -115,7 +131,7 @@ public void shouldNotChallengeDuringWindowWithoutSubject() throws Exception
.key(RFC7515_RS256_CONFIG)
.challenge(challenge)
.build();
- JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
+ JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
Instant now = Instant.now();
@@ -127,7 +143,7 @@ public void shouldNotChallengeDuringWindowWithoutSubject() throws Exception
String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionId = guard.reauthorize(101L, token);
+ long sessionId = guard.reauthorize(0L, 0L, 101L, token);
assertFalse(guard.challenge(sessionId, now.plusSeconds(8L).toEpochMilli()));
}
@@ -143,7 +159,7 @@ public void shouldNotChallengeBeforeChallengeWindow() throws Exception
.key(RFC7515_RS256_CONFIG)
.challenge(challenge)
.build();
- JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
+ JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
Instant now = Instant.now();
@@ -156,7 +172,7 @@ public void shouldNotChallengeBeforeChallengeWindow() throws Exception
String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionId = guard.reauthorize(101L, token);
+ long sessionId = guard.reauthorize(0L, 0L, 101L, token);
assertFalse(guard.challenge(sessionId, now.plusSeconds(5L).toEpochMilli()));
}
@@ -172,7 +188,7 @@ public void shouldNotChallengeAgainDuringChallengeWindow() throws Exception
.key(RFC7515_RS256_CONFIG)
.challenge(challenge)
.build();
- JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
+ JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
Instant now = Instant.now();
@@ -185,7 +201,7 @@ public void shouldNotChallengeAgainDuringChallengeWindow() throws Exception
String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionId = guard.reauthorize(101L, token);
+ long sessionId = guard.reauthorize(0L, 0L, 101L, token);
assertTrue(guard.challenge(sessionId, now.plusSeconds(8L).toEpochMilli()));
assertFalse(guard.challenge(sessionId, now.plusSeconds(8L).toEpochMilli()));
@@ -201,7 +217,7 @@ public void shouldNotAuthorizeWhenAlgorithmDiffers() throws Exception
.audience("testAudience")
.key(RFC7515_RS256_CONFIG)
.build();
- JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
+ JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
JwtClaims claims = new JwtClaims();
claims.setClaim("iss", "test issuer");
@@ -209,7 +225,7 @@ public void shouldNotAuthorizeWhenAlgorithmDiffers() throws Exception
String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS512");
- long sessionId = guard.reauthorize(101L, token);
+ long sessionId = guard.reauthorize(0L, 0L, 101L, token);
assertThat(sessionId, equalTo(0L));
}
@@ -223,7 +239,7 @@ public void shouldNotAuthorizeWhenSignatureInvalid() throws Exception
.audience("testAudience")
.key(RFC7515_RS256_CONFIG)
.build();
- JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
+ JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
JwtClaims claims = new JwtClaims();
claims.setClaim("iss", "test issuer");
@@ -233,7 +249,7 @@ public void shouldNotAuthorizeWhenSignatureInvalid() throws Exception
.replaceFirst("\\.[^X]", ".X")
.replaceFirst("\\.[^Y]", ".Y");
- long sessionId = guard.reauthorize(101L, token);
+ long sessionId = guard.reauthorize(0L, 0L, 101L, token);
assertThat(sessionId, equalTo(0L));
}
@@ -247,7 +263,7 @@ public void shouldNotAuthorizeWhenIssuerDiffers() throws Exception
.audience("testAudience")
.key(RFC7515_RS256_CONFIG)
.build();
- JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
+ JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
JwtClaims claims = new JwtClaims();
claims.setClaim("iss", "not test issuer");
@@ -255,7 +271,7 @@ public void shouldNotAuthorizeWhenIssuerDiffers() throws Exception
String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionId = guard.reauthorize(101L, token);
+ long sessionId = guard.reauthorize(0L, 0L, 101L, token);
assertThat(sessionId, equalTo(0L));
}
@@ -269,7 +285,7 @@ public void shouldNotAuthorizeWhenAudienceDiffers() throws Exception
.audience("testAudience")
.key(RFC7515_RS256_CONFIG)
.build();
- JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
+ JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
JwtClaims claims = new JwtClaims();
claims.setClaim("iss", "test issuer");
@@ -277,7 +293,7 @@ public void shouldNotAuthorizeWhenAudienceDiffers() throws Exception
String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionId = guard.reauthorize(101L, token);
+ long sessionId = guard.reauthorize(0L, 0L, 101L, token);
assertThat(sessionId, equalTo(0L));
}
@@ -291,7 +307,7 @@ public void shouldNotAuthorizeWhenExpired() throws Exception
.audience("testAudience")
.key(RFC7515_RS256_CONFIG)
.build();
- JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
+ JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
Instant now = Instant.now();
@@ -302,7 +318,7 @@ public void shouldNotAuthorizeWhenExpired() throws Exception
String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionId = guard.reauthorize(101L, token);
+ long sessionId = guard.reauthorize(0L, 0L, 101L, token);
assertThat(sessionId, equalTo(0L));
}
@@ -316,7 +332,7 @@ public void shouldNotAuthorizeWhenNotYetValid() throws Exception
.audience("testAudience")
.key(RFC7515_RS256_CONFIG)
.build();
- JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
+ JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
Instant now = Instant.now();
@@ -327,7 +343,7 @@ public void shouldNotAuthorizeWhenNotYetValid() throws Exception
String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionId = guard.reauthorize(101L, token);
+ long sessionId = guard.reauthorize(0L, 0L, 101L, token);
assertThat(sessionId, equalTo(0L));
}
@@ -343,7 +359,7 @@ public void shouldNotVerifyAuthorizedWhenRolesInsufficient() throws Exception
.key(RFC7515_RS256_CONFIG)
.challenge(challenge)
.build();
- JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
+ JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
JwtClaims claims = new JwtClaims();
claims.setClaim("iss", "test issuer");
@@ -352,7 +368,7 @@ public void shouldNotVerifyAuthorizedWhenRolesInsufficient() throws Exception
String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionId = guard.reauthorize(101L, token);
+ long sessionId = guard.reauthorize(0L, 0L, 101L, token);
assertThat(sessionId, not(equalTo(0L)));
assertFalse(guard.verify(sessionId, asList("read:stream", "write:stream")));
@@ -369,7 +385,7 @@ public void shouldReauthorizeWhenExpirationLater() throws Exception
.key(RFC7515_RS256_CONFIG)
.challenge(challenge)
.build();
- JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
+ JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
Instant now = Instant.now();
@@ -382,12 +398,12 @@ public void shouldReauthorizeWhenExpirationLater() throws Exception
String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10);
+ long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10);
claims.setClaim("exp", now.getEpochSecond() + 60L);
String tokenPlus60 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionIdPlus60 = guard.reauthorize(101L, tokenPlus60);
+ long sessionIdPlus60 = guard.reauthorize(0L, 0L, 101L, tokenPlus60);
assertThat(sessionIdPlus60, equalTo(sessionIdPlus10));
assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 60L).toMillis()));
@@ -404,7 +420,7 @@ public void shouldReauthorizeWhenScopeBroader() throws Exception
.key(RFC7515_RS256_CONFIG)
.challenge(challenge)
.build();
- JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
+ JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
Instant now = Instant.now();
@@ -417,13 +433,13 @@ public void shouldReauthorizeWhenScopeBroader() throws Exception
String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10);
+ long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10);
claims.setClaim("exp", now.getEpochSecond() + 60L);
claims.setClaim("scope", "read:stream write:stream");
String tokenPlus60 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionIdPlus60 = guard.reauthorize(101L, tokenPlus60);
+ long sessionIdPlus60 = guard.reauthorize(0L, 0L, 101L, tokenPlus60);
assertThat(sessionIdPlus60, equalTo(sessionIdPlus10));
assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 60L).toMillis()));
@@ -440,7 +456,7 @@ public void shouldNotReauthorizeWhenExpirationEarlier() throws Exception
.key(RFC7515_RS256_CONFIG)
.challenge(challenge)
.build();
- JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
+ JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
Instant now = Instant.now();
@@ -453,12 +469,12 @@ public void shouldNotReauthorizeWhenExpirationEarlier() throws Exception
String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10);
+ long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10);
claims.setClaim("exp", now.getEpochSecond() + 5L);
String tokenPlus5 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionIdPlus5 = guard.reauthorize(101L, tokenPlus5);
+ long sessionIdPlus5 = guard.reauthorize(0L, 0L, 101L, tokenPlus5);
assertThat(sessionIdPlus5, equalTo(sessionIdPlus10));
assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 10L).toMillis()));
@@ -475,7 +491,7 @@ public void shouldNotReauthorizeWhenScopeNarrower() throws Exception
.key(RFC7515_RS256_CONFIG)
.challenge(challenge)
.build();
- JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
+ JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
Instant now = Instant.now();
@@ -488,13 +504,13 @@ public void shouldNotReauthorizeWhenScopeNarrower() throws Exception
String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10);
+ long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10);
claims.setClaim("exp", now.getEpochSecond() + 60L);
claims.setClaim("scope", "read:stream");
String tokenPlus60 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionIdPlus60 = guard.reauthorize(101L, tokenPlus60);
+ long sessionIdPlus60 = guard.reauthorize(0L, 0L, 101L, tokenPlus60);
assertThat(sessionIdPlus60, not(equalTo(sessionIdPlus10)));
assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 10L).toMillis()));
@@ -512,7 +528,7 @@ public void shouldNotReauthorizeWhenSubjectDiffers() throws Exception
.key(RFC7515_RS256_CONFIG)
.challenge(challenge)
.build();
- JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
+ JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
Instant now = Instant.now();
@@ -525,13 +541,13 @@ public void shouldNotReauthorizeWhenSubjectDiffers() throws Exception
String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10);
+ long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10);
claims.setClaim("sub", "otherSubject");
claims.setClaim("exp", now.getEpochSecond() + 60L);
String tokenPlus60 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionIdPlus60 = guard.reauthorize(101L, tokenPlus60);
+ long sessionIdPlus60 = guard.reauthorize(0L, 0L, 101L, tokenPlus60);
assertThat(sessionIdPlus60, not(equalTo(sessionIdPlus10)));
assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 10L).toMillis()));
@@ -549,7 +565,7 @@ public void shouldNotReauthorizeWhenContextDiffers() throws Exception
.key(RFC7515_RS256_CONFIG)
.challenge(challenge)
.build();
- JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
+ JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
Instant now = Instant.now();
@@ -562,12 +578,12 @@ public void shouldNotReauthorizeWhenContextDiffers() throws Exception
String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10);
+ long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10);
claims.setClaim("exp", now.getEpochSecond() + 60L);
String tokenPlus60 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionIdPlus60 = guard.reauthorize(202L, tokenPlus60);
+ long sessionIdPlus60 = guard.reauthorize(0L, 0L, 202L, tokenPlus60);
assertThat(sessionIdPlus60, not(equalTo(sessionIdPlus10)));
assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 10L).toMillis()));
@@ -585,7 +601,7 @@ public void shouldDeauthorize() throws Exception
.key(RFC7515_RS256_CONFIG)
.challenge(challenge)
.build();
- JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
+ JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL);
Instant now = Instant.now();
@@ -598,7 +614,7 @@ public void shouldDeauthorize() throws Exception
String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionId = guard.reauthorize(101L, token);
+ long sessionId = guard.reauthorize(0L, 0L, 101L, token);
guard.deauthorize(sessionId);
}
diff --git a/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardTest.java b/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardTest.java
index 9d54435ab1..1f41b642a8 100644
--- a/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardTest.java
+++ b/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardTest.java
@@ -167,7 +167,7 @@ public void shouldNotVerifyRolesWhenInsufficient() throws Exception
String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long authorizedId = handler.reauthorize(101L, token);
+ long authorizedId = handler.reauthorize(0L, 0L, 101L, token);
assertFalse(verifier.test(authorizedId));
}
@@ -220,7 +220,7 @@ public void shouldVerifyRolesWhenExact() throws Exception
String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionId = handler.reauthorize(101L, token);
+ long sessionId = handler.reauthorize(0L, 0L, 101L, token);
assertTrue(verifier.test(sessionId));
}
@@ -272,7 +272,7 @@ public void shouldVerifyRolesWhenSuperset() throws Exception
String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionId = handler.reauthorize(101L, token);
+ long sessionId = handler.reauthorize(0L, 0L, 101L, token);
assertTrue(verifier.test(sessionId));
}
@@ -323,7 +323,7 @@ public void shouldVerifyRolesWhenEmpty() throws Exception
String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionId = handler.reauthorize(101L, token);
+ long sessionId = handler.reauthorize(0L, 0L, 101L, token);
assertTrue(verifier.test(sessionId));
}
@@ -374,7 +374,7 @@ public void shouldVerifyWhenIndexDiffers() throws Exception
String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionId = handler.reauthorize(101L, token);
+ long sessionId = handler.reauthorize(0L, 0L, 101L, token);
assertTrue(verifier.test(sessionId));
}
@@ -425,7 +425,7 @@ public void shouldIdentify() throws Exception
String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionId = handler.reauthorize(101L, token);
+ long sessionId = handler.reauthorize(0L, 0L, 101L, token);
assertEquals("testSubject", identifier.apply(sessionId));
}
@@ -478,7 +478,7 @@ public void shouldIdentifyWhenIndexDiffers() throws Exception
String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256");
- long sessionId = handler.reauthorize(101L, token);
+ long sessionId = handler.reauthorize(0L, 0L, 101L, token);
assertEquals("testSubject", identifier.apply(sessionId));
}
diff --git a/specs/binding-http.spec/src/main/resources/META-INF/zilla/http.idl b/specs/binding-http.spec/src/main/resources/META-INF/zilla/http.idl
index 20887c8bdd..5bffe232f1 100644
--- a/specs/binding-http.spec/src/main/resources/META-INF/zilla/http.idl
+++ b/specs/binding-http.spec/src/main/resources/META-INF/zilla/http.idl
@@ -49,4 +49,26 @@ scope http
HttpHeader[] headers;
}
}
+
+ scope event
+ {
+ enum HttpEventType (uint8)
+ {
+ REQUEST_ACCEPTED (1)
+ }
+
+ struct HttpRequestAccepted extends core::event::Event
+ {
+ string8 identity;
+ string8 scheme;
+ string8 method;
+ string16 authority;
+ string16 path;
+ }
+
+ union HttpEvent switch (HttpEventType)
+ {
+ case REQUEST_ACCEPTED: HttpRequestAccepted requestAccepted;
+ }
+ }
}
diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/client.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/client.rpt
index 90377a5931..2369372d0d 100644
--- a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/client.rpt
+++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/client.rpt
@@ -33,7 +33,7 @@ connected
write "GET / HTTP/1.1" "\r\n"
"Host: example.com:9090" "\r\n"
- "Authorization: Bearer EXPIRED" "\r\n"
+ "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImV4YW1wbGUifQ.eyJhdWQiOiJodHRwczovL2FwaS5leGFtcGxlLmNvbSIsImV4cCI6MTcwODk0MDQ0MCwiaXNzIjoiaHR0cHM6Ly9hdXRoLmV4YW1wbGUuY29tIiwic3ViIjoidXNlciJ9.KwZt_rDTDEOQ33URwZ8Gd1WqQjAIJESbt5Et309Yz7AJm1wWWyFhWm7AV6lt1rkX-eD-jVh0wHAsy7L4kdzBOErCdwFcdaB4u2jFDGn_9IK28lCedxIYmYtI4qn6eY916IIqRpwZcqzw08OEljfYUKo4UeX7JPAtha0GQmfZY1-NNcncg06xw3xkKSZ1SnIh9MZM1FNH_5QPZPL4NHP7DRXtaMn2w6YpO7n695Sc_3LuSDlfDDMVIUWuEreOzOXem6jheGIbJ-eDKhIXXPrOz1NBAJmvizbugMR7m_bodiEzzqt5ttKDs1974alrR_sYP8OYmR_rCqXc5N3lp_SW3A" "\r\n"
"\r\n"
write flush
diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/server.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/server.rpt
index 5761cef887..f091a35b5d 100644
--- a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/server.rpt
+++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/server.rpt
@@ -32,7 +32,7 @@ connected
read "GET / HTTP/1.1" "\r\n"
"Host: example.com:9090" "\r\n"
- "Authorization: Bearer EXPIRED" "\r\n"
+ "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImV4YW1wbGUifQ.eyJhdWQiOiJodHRwczovL2FwaS5leGFtcGxlLmNvbSIsImV4cCI6MTcwODk0MDQ0MCwiaXNzIjoiaHR0cHM6Ly9hdXRoLmV4YW1wbGUuY29tIiwic3ViIjoidXNlciJ9.KwZt_rDTDEOQ33URwZ8Gd1WqQjAIJESbt5Et309Yz7AJm1wWWyFhWm7AV6lt1rkX-eD-jVh0wHAsy7L4kdzBOErCdwFcdaB4u2jFDGn_9IK28lCedxIYmYtI4qn6eY916IIqRpwZcqzw08OEljfYUKo4UeX7JPAtha0GQmfZY1-NNcncg06xw3xkKSZ1SnIh9MZM1FNH_5QPZPL4NHP7DRXtaMn2w6YpO7n695Sc_3LuSDlfDDMVIUWuEreOzOXem6jheGIbJ-eDKhIXXPrOz1NBAJmvizbugMR7m_bodiEzzqt5ttKDs1974alrR_sYP8OYmR_rCqXc5N3lp_SW3A" "\r\n"
"\r\n"
read closed
diff --git a/specs/binding-kafka.spec/src/main/resources/META-INF/zilla/kafka.idl b/specs/binding-kafka.spec/src/main/resources/META-INF/zilla/kafka.idl
index a3c0874d9d..a9f3222680 100644
--- a/specs/binding-kafka.spec/src/main/resources/META-INF/zilla/kafka.idl
+++ b/specs/binding-kafka.spec/src/main/resources/META-INF/zilla/kafka.idl
@@ -533,4 +533,25 @@ scope kafka
int32 index;
}
}
+
+ scope event
+ {
+ enum KafkaEventType (uint8)
+ {
+ AUTHORIZATION_FAILED (1),
+ API_VERSION_REJECTED (2)
+ }
+
+ struct KafkaApiVersionRejected extends core::event::Event
+ {
+ int32 apiKey;
+ int32 apiVersion;
+ }
+
+ union KafkaEvent switch (KafkaEventType)
+ {
+ case AUTHORIZATION_FAILED: core::event::Event authorizationFailed;
+ case API_VERSION_REJECTED: KafkaApiVersionRejected apiVersionRejected;
+ }
+ }
}
diff --git a/specs/binding-mqtt.spec/src/main/resources/META-INF/zilla/mqtt.idl b/specs/binding-mqtt.spec/src/main/resources/META-INF/zilla/mqtt.idl
index 26730b7747..9431a6b159 100644
--- a/specs/binding-mqtt.spec/src/main/resources/META-INF/zilla/mqtt.idl
+++ b/specs/binding-mqtt.spec/src/main/resources/META-INF/zilla/mqtt.idl
@@ -260,6 +260,5 @@ scope mqtt
COMPLETE(0),
INCOMPLETE(1)
}
-
}
}
diff --git a/specs/binding-tcp.spec/pom.xml b/specs/binding-tcp.spec/pom.xml
index 3a64fc98ba..94ef266ac7 100644
--- a/specs/binding-tcp.spec/pom.xml
+++ b/specs/binding-tcp.spec/pom.xml
@@ -73,6 +73,22 @@
org.jasig.maven
maven-notice-plugin
+
+ ${project.groupId}
+ flyweight-maven-plugin
+ ${project.version}
+
+ core tcp proxy
+ io.aklivity.zilla.specs.binding.tcp.internal.types
+
+
+
+
+ generate
+
+
+
+
com.mycila
license-maven-plugin
diff --git a/specs/binding-tcp.spec/src/main/resources/META-INF/zilla/tcp.idl b/specs/binding-tcp.spec/src/main/resources/META-INF/zilla/tcp.idl
new file mode 100644
index 0000000000..a796c117f5
--- /dev/null
+++ b/specs/binding-tcp.spec/src/main/resources/META-INF/zilla/tcp.idl
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+scope tcp
+{
+ scope event
+ {
+ enum TcpEventType (uint8)
+ {
+ DNS_FAILED (1)
+ }
+
+ struct TcpDnsFailed extends core::event::Event
+ {
+ string16 address;
+ }
+
+ union TcpEvent switch (TcpEventType)
+ {
+ case DNS_FAILED: TcpDnsFailed dnsFailed;
+ }
+ }
+}
diff --git a/specs/binding-tls.spec/pom.xml b/specs/binding-tls.spec/pom.xml
index dd0ca5341e..d38dbf81e7 100644
--- a/specs/binding-tls.spec/pom.xml
+++ b/specs/binding-tls.spec/pom.xml
@@ -78,6 +78,22 @@
org.jasig.maven
maven-notice-plugin
+
+ ${project.groupId}
+ flyweight-maven-plugin
+ ${project.version}
+
+ core tls proxy
+ io.aklivity.zilla.specs.binding.tls.internal.types
+
+
+
+
+ generate
+
+
+
+
com.mycila
license-maven-plugin
diff --git a/specs/binding-tls.spec/src/main/resources/META-INF/zilla/tls.idl b/specs/binding-tls.spec/src/main/resources/META-INF/zilla/tls.idl
new file mode 100644
index 0000000000..c81e96e2c7
--- /dev/null
+++ b/specs/binding-tls.spec/src/main/resources/META-INF/zilla/tls.idl
@@ -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.
+ */
+scope tls
+{
+ scope event
+ {
+ enum TlsEventType (uint8)
+ {
+ TLS_FAILED (1),
+ TLS_PROTOCOL_REJECTED (2),
+ TLS_KEY_REJECTED (3),
+ TLS_PEER_NOT_VERIFIED (4),
+ TLS_HANDSHAKE_FAILED (5)
+ }
+
+ union TlsEvent switch (TlsEventType)
+ {
+ case TLS_FAILED: core::event::Event tlsFailed;
+ case TLS_PROTOCOL_REJECTED: core::event::Event tlsProtocolRejected;
+ case TLS_KEY_REJECTED: core::event::Event tlsKeyRejected;
+ case TLS_PEER_NOT_VERIFIED: core::event::Event tlsPeerNotVerified;
+ case TLS_HANDSHAKE_FAILED: core::event::Event tlsHandshakeFailed;
+ }
+ }
+}
diff --git a/specs/engine.spec/src/main/resources/META-INF/zilla/core.idl b/specs/engine.spec/src/main/resources/META-INF/zilla/core.idl
index bc78dd0888..05dfc800aa 100644
--- a/specs/engine.spec/src/main/resources/META-INF/zilla/core.idl
+++ b/specs/engine.spec/src/main/resources/META-INF/zilla/core.idl
@@ -100,4 +100,14 @@ scope core
octets extension;
}
}
+
+ scope event
+ {
+ struct Event
+ {
+ int64 timestamp;
+ int64 traceId;
+ int64 namespacedId;
+ }
+ }
}
diff --git a/specs/guard-jwt.spec/pom.xml b/specs/guard-jwt.spec/pom.xml
index ebe02ac328..ab5d123aed 100644
--- a/specs/guard-jwt.spec/pom.xml
+++ b/specs/guard-jwt.spec/pom.xml
@@ -63,6 +63,23 @@
org.jasig.maven
maven-notice-plugin
+
+ ${project.groupId}
+ flyweight-maven-plugin
+ ${project.version}
+
+ core jwt
+ io.aklivity.zilla.specs.guard.jwt.internal.types
+
+
+
+
+ validate
+ generate
+
+
+
+
com.mycila
license-maven-plugin
@@ -86,6 +103,9 @@
org.jacoco
jacoco-maven-plugin
+
+ io/aklivity/zilla/specs/guard/jwt/internal/types/**/*.class
+
BUNDLE
diff --git a/specs/guard-jwt.spec/src/main/resources/META-INF/zilla/jwt.idl b/specs/guard-jwt.spec/src/main/resources/META-INF/zilla/jwt.idl
new file mode 100644
index 0000000000..997f9bf859
--- /dev/null
+++ b/specs/guard-jwt.spec/src/main/resources/META-INF/zilla/jwt.idl
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+scope jwt
+{
+ scope event
+ {
+ enum JwtEventType (uint8)
+ {
+ AUTHORIZATION_FAILED (1)
+ }
+
+ struct JwtAuthorizationFailed extends core::event::Event
+ {
+ string8 identity;
+ }
+
+ union JwtEvent switch (JwtEventType)
+ {
+ case AUTHORIZATION_FAILED: JwtAuthorizationFailed authorizationFailed;
+ }
+ }
+}