diff --git a/runtime/binding-filesystem/src/main/java/io/aklivity/zilla/runtime/binding/filesystem/internal/stream/FileSystemServerFactory.java b/runtime/binding-filesystem/src/main/java/io/aklivity/zilla/runtime/binding/filesystem/internal/stream/FileSystemServerFactory.java index 182e53b36b..62c5ca3663 100644 --- a/runtime/binding-filesystem/src/main/java/io/aklivity/zilla/runtime/binding/filesystem/internal/stream/FileSystemServerFactory.java +++ b/runtime/binding-filesystem/src/main/java/io/aklivity/zilla/runtime/binding/filesystem/internal/stream/FileSystemServerFactory.java @@ -15,14 +15,23 @@ package io.aklivity.zilla.runtime.binding.filesystem.internal.stream; import static io.aklivity.zilla.runtime.binding.filesystem.config.FileSystemSymbolicLinksConfig.IGNORE; +import static io.aklivity.zilla.runtime.binding.filesystem.internal.types.stream.FileSystemError.FILE_ALREADY_EXISTS; +import static io.aklivity.zilla.runtime.binding.filesystem.internal.types.stream.FileSystemError.FILE_MODIFIED; +import static io.aklivity.zilla.runtime.binding.filesystem.internal.types.stream.FileSystemError.FILE_NOT_FOUND; +import static io.aklivity.zilla.runtime.binding.filesystem.internal.types.stream.FileSystemError.FILE_TAG_MISSING; import static io.aklivity.zilla.runtime.engine.budget.BudgetDebitor.NO_DEBITOR_INDEX; import static java.nio.file.LinkOption.NOFOLLOW_LINKS; +import static java.nio.file.StandardCopyOption.ATOMIC_MOVE; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import static java.nio.file.StandardOpenOption.CREATE; +import static java.nio.file.StandardOpenOption.WRITE; import static java.time.Instant.now; import static org.agrona.LangUtil.rethrowUnchecked; import java.io.IOException; import java.io.InputStream; import java.net.URI; +import java.nio.channels.ByteChannel; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.LinkOption; @@ -55,6 +64,9 @@ import io.aklivity.zilla.runtime.binding.filesystem.internal.types.stream.DataFW; import io.aklivity.zilla.runtime.binding.filesystem.internal.types.stream.EndFW; import io.aklivity.zilla.runtime.binding.filesystem.internal.types.stream.FileSystemBeginExFW; +import io.aklivity.zilla.runtime.binding.filesystem.internal.types.stream.FileSystemError; +import io.aklivity.zilla.runtime.binding.filesystem.internal.types.stream.FileSystemErrorFW; +import io.aklivity.zilla.runtime.binding.filesystem.internal.types.stream.FileSystemResetExFW; import io.aklivity.zilla.runtime.binding.filesystem.internal.types.stream.ResetFW; import io.aklivity.zilla.runtime.binding.filesystem.internal.types.stream.SignalFW; import io.aklivity.zilla.runtime.binding.filesystem.internal.types.stream.WindowFW; @@ -73,11 +85,17 @@ public final class FileSystemServerFactory implements FileSystemStreamFactory private static final OctetsFW EMPTY_EXTENSION = new OctetsFW().wrap(new UnsafeBuffer(new byte[0]), 0, 0); private static final int READ_PAYLOAD_MASK = 1 << FileSystemCapabilities.READ_PAYLOAD.ordinal(); + private static final int WRITE_PAYLOAD_MASK = 1 << FileSystemCapabilities.WRITE_PAYLOAD.ordinal(); + private static final int CREATE_PAYLOAD_MASK = 1 << FileSystemCapabilities.CREATE_PAYLOAD.ordinal(); + private static final int DELETE_PAYLOAD_MASK = 1 << FileSystemCapabilities.DELETE_PAYLOAD.ordinal(); private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream"; private static final int TIMEOUT_EXPIRED_SIGNAL_ID = 0; public static final int FILE_CHANGED_SIGNAL_ID = 1; + private static final int FLAG_FIN = 0x01; + private static final int FLAG_INIT = 0x02; private final BeginFW beginRO = new BeginFW(); + private final DataFW dataRO = new DataFW(); private final EndFW endRO = new EndFW(); private final AbortFW abortRO = new AbortFW(); private final WindowFW windowRO = new WindowFW(); @@ -94,12 +112,15 @@ public final class FileSystemServerFactory implements FileSystemStreamFactory private final WindowFW.Builder windowRW = new WindowFW.Builder(); private final FileSystemBeginExFW.Builder beginExRW = new FileSystemBeginExFW.Builder(); + private final FileSystemResetExFW.Builder resetExRW = new FileSystemResetExFW.Builder(); + private final FileSystemErrorFW.Builder errorExRW = new FileSystemErrorFW.Builder(); private final OctetsFW payloadRO = new OctetsFW(); private final Long2ObjectHashMap bindings; private final BufferPool bufferPool; private final MutableDirectBuffer writeBuffer; private final MutableDirectBuffer extBuffer; + private final MutableDirectBuffer errorBuffer; private final MutableDirectBuffer readBuffer; private final LongFunction supplyDebitor; private final LongUnaryOperator supplyReplyId; @@ -109,6 +130,8 @@ public final class FileSystemServerFactory implements FileSystemStreamFactory private final Signaler signaler; private final Supplier supplyWatcher; + private final int decodeMax; + private FileSystemWatcher fileSystemWatcher; public FileSystemServerFactory( @@ -121,6 +144,7 @@ public FileSystemServerFactory( this.writeBuffer = context.writeBuffer(); this.extBuffer = new UnsafeBuffer(new byte[writeBuffer.capacity()]); this.readBuffer = new UnsafeBuffer(new byte[writeBuffer.capacity()]); + this.errorBuffer = new UnsafeBuffer(new byte[1]); this.supplyDebitor = context::supplyDebitor; this.supplyReplyId = context::supplyReplyId; this.fileSystemTypeId = context.supplyTypeId(FileSystemBinding.NAME); @@ -128,6 +152,7 @@ public FileSystemServerFactory( this.signaler = context.signaler(); this.md5 = initMessageDigest("MD5"); this.supplyWatcher = supplyWatcher; + this.decodeMax = bufferPool.slotCapacity(); } @Override @@ -181,10 +206,10 @@ public MessageConsumer newStream( final String tag = beginEx.tag().asString(); try { - if (Files.exists(Paths.get(resolvedPath), symlinks)) + if (writeOperation(capabilities)) { String type = probeContentTypeOrDefault(path); - newStream = new FileSystemServer( + newStream = new FileSystemServerWriter( app, originId, routedId, @@ -196,6 +221,25 @@ public MessageConsumer newStream( capabilities, tag)::onAppMessage; } + else + { + if (Files.exists(Paths.get(resolvedPath), symlinks)) + { + String type = probeContentTypeOrDefault(path); + newStream = new FileSystemServerReader( + app, + originId, + routedId, + initialId, + type, + symlinks, + relativePath, + resolvedPath, + capabilities, + tag)::onAppMessage; + } + } + } catch (IOException ex) { @@ -228,7 +272,7 @@ private String probeContentTypeOrDefault( return contentType != null ? contentType : DEFAULT_CONTENT_TYPE; } - private final class FileSystemServer + private final class FileSystemServerReader { private final MessageConsumer app; private final long originId; @@ -260,7 +304,7 @@ private final class FileSystemServer private int state; - private FileSystemServer( + private FileSystemServerReader( MessageConsumer app, long originId, long routedId, @@ -602,7 +646,8 @@ private void doAppReset( { state = FileSystemState.closeInitial(state); - doReset(app, originId, routedId, initialId, initialSeq, initialAck, initialMax, traceId, 0L); + doReset(app, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, 0L, EMPTY_EXTENSION); } } @@ -687,6 +732,490 @@ private void flushAppData( } } + private final class FileSystemServerWriter + { + private final MessageConsumer app; + private final long originId; + private final long routedId; + private final long initialId; + private final long replyId; + private final String type; + private final String relativePath; + private final Path resolvedPath; + private final int capabilities; + private final String tag; + private final LinkOption[] symlinks; + private long initialSeq; + private long initialAck; + private int initialMax; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + private long replyBud; + + private int state; + + private ByteChannel out; + private Path tmpPath; + + private FileSystemServerWriter( + MessageConsumer app, + long originId, + long routedId, + long initialId, + String type, + LinkOption[] symlinks, + String relativePath, + String resolvedPath, + int capabilities, + String tag) + { + this.app = app; + this.originId = originId; + this.routedId = routedId; + this.initialId = initialId; + this.replyId = supplyReplyId.applyAsLong(initialId); + this.type = type; + this.symlinks = symlinks; + this.relativePath = relativePath; + this.resolvedPath = Paths.get(resolvedPath); + this.capabilities = capabilities; + this.tag = tag != null && !tag.isEmpty() ? tag : null; + this.initialMax = decodeMax; + } + + private void onAppMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onAppBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onAppData(data); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onAppEnd(end); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onAppAbort(abort); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onAppWindow(window); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onAppReset(reset); + break; + default: + break; + } + } + + private void onAppBegin( + BeginFW begin) + { + final long sequence = begin.sequence(); + final long acknowledge = begin.acknowledge(); + final long traceId = begin.traceId(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + assert acknowledge >= initialAck; + + initialSeq = sequence; + initialAck = acknowledge; + + assert initialAck <= initialSeq; + + state = FileSystemState.openingInitial(state); + + FileSystemError error = null; + + if ((capabilities & CREATE_PAYLOAD_MASK) != 0 && Files.exists(resolvedPath)) + { + error = FILE_ALREADY_EXISTS; + } + else if ((capabilities & WRITE_PAYLOAD_MASK) != 0) + { + if (Files.notExists(resolvedPath)) + { + error = FILE_NOT_FOUND; + } + else if (tag == null) + { + error = FILE_TAG_MISSING; + } + else if (!validateTag()) + { + error = FILE_MODIFIED; + } + } + else if ((capabilities & DELETE_PAYLOAD_MASK) != 0) + { + if (Files.notExists(resolvedPath)) + { + error = FILE_NOT_FOUND; + + } + else if (tag != null && !validateTag()) + { + error = FILE_MODIFIED; + } + else + { + delete(traceId); + } + } + + if (error != null) + { + errorExRW.wrap(errorBuffer, 0, errorBuffer.capacity()).set(error); + + FileSystemResetExFW extension = resetExRW + .wrap(extBuffer, 0, extBuffer.capacity()) + .typeId(fileSystemTypeId) + .error(errorExRW.build()) + .build(); + + doAppReset(traceId, extension); + } + + doAppWindow(traceId); + } + + private void onAppData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final int flags = data.flags(); + final int reserved = data.reserved(); + final OctetsFW payload = data.payload(); + + int offset = payload.offset(); + int length = payload.sizeof(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + if (initialSeq > initialAck + initialMax) + { + cleanup(traceId); + } + else + { + try + { + if ((flags & FLAG_INIT) != 0x00) + { + out = getOutputStream(); + } + + assert out != null; + + out.write(payload.buffer().byteBuffer().slice(offset, length)); + + if ((flags & FLAG_FIN) != 0x00) + { + out.close(); + + if ((capabilities & WRITE_PAYLOAD_MASK) != 0) + { + Files.move(tmpPath, resolvedPath, REPLACE_EXISTING, ATOMIC_MOVE); + } + + String currentTag = calculateTag(); + doAppBegin(traceId, currentTag); + doAppEnd(traceId); + } + doAppWindow(traceId); + } + catch (Exception ex) + { + cleanup(traceId); + } + } + } + + private void onAppEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + + assert initialAck <= initialSeq; + + state = FileSystemState.closeInitial(state); + } + + private void onAppAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + + assert initialAck <= initialSeq; + + state = FileSystemState.closeInitial(state); + + cleanupTmpFileIfExists(); + + doAppAbort(traceId); + } + + private void onAppWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final int padding = window.padding(); + final long budgetId = window.budgetId(); + final long traceId = window.traceId(); + + assert acknowledge <= sequence; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + replyPad = padding; + replyBud = budgetId; + + assert replyAck <= replySeq; + + if (FileSystemState.replyOpening(state) && !FileSystemState.replyClosed(state)) + { + state = FileSystemState.openReply(state); + } + } + + private void onAppReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert acknowledge >= replyAck; + + replyAck = acknowledge; + + assert replyAck <= replySeq; + + state = FileSystemState.closeReply(state); + + cleanupTmpFileIfExists(); + + doAppReset(traceId, EMPTY_EXTENSION); + } + + private void doAppBegin( + long traceId, + String tag) + { + doAppBegin(traceId, tag, capabilities); + } + + private void doAppBegin( + long traceId, + String tag, + int capabilities) + { + state = FileSystemState.openingReply(state); + Flyweight extension = beginExRW + .wrap(extBuffer, 0, extBuffer.capacity()) + .typeId(fileSystemTypeId) + .capabilities(capabilities) + .path(relativePath) + .tag(tag) + .build(); + doBegin(app, originId, routedId, replyId, replySeq, replyAck, replyMax, traceId, 0L, 0L, extension); + } + + private void doAppEnd( + long traceId) + { + if (FileSystemState.replyOpening(state) && !FileSystemState.replyClosed(state)) + { + state = FileSystemState.closeReply(state); + doEnd(app, originId, routedId, replyId, replySeq, replyAck, replyMax, traceId, 0L, EMPTY_EXTENSION); + } + } + + private void doAppAbort( + long traceId) + { + if (FileSystemState.replyOpening(state) && !FileSystemState.replyClosed(state)) + { + state = FileSystemState.closeReply(state); + doAbort(app, originId, routedId, replyId, replySeq, replyAck, + replyMax, traceId, 0L, EMPTY_EXTENSION); + } + } + + private void doAppReset( + long traceId, + Flyweight extension) + { + if (FileSystemState.initialOpening(state) && !FileSystemState.initialClosed(state)) + { + state = FileSystemState.closeInitial(state); + + doReset(app, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, 0L, extension); + } + } + + private void doAppWindow( + long traceId) + { + if (!FileSystemState.initialClosed(state)) + { + state = FileSystemState.openInitial(state); + + doWindow(app, originId, routedId, initialId, initialSeq, initialAck, initialMax, traceId, + 0L, 0L, 0); + } + } + + private void cleanup( + long traceId) + { + cleanupTmpFileIfExists(); + + doAppAbort(traceId); + doAppReset(traceId, EMPTY_EXTENSION); + } + + private void cleanupTmpFileIfExists() + { + if (tmpPath != null) + { + try + { + Files.deleteIfExists(tmpPath); + } + catch (IOException ex) + { + rethrowUnchecked(ex); + } + } + } + + private String calculateTag() + { + String newTag = null; + try + { + InputStream input = getInputStream(); + if (input != null) + { + md5.reset(); + while (input.available() > 0) + { + final byte[] readArray = readBuffer.byteArray(); + int bytesRead = input.read(readArray, 0, readArray.length); + md5.update(readArray, 0, Math.max(bytesRead, 0)); + } + byte[] hash = md5.digest(); + newTag = BitUtil.toHex(hash); + } + } + catch (IOException ex) + { + // reject + } + return newTag; + } + + private boolean validateTag() + { + return tag.equals(calculateTag()); + } + + private InputStream getInputStream() + { + InputStream input = null; + try + { + input = Files.newInputStream(resolvedPath, symlinks); + } + catch (IOException ex) + { + // reject + } + return input; + } + + private ByteChannel getOutputStream() + { + ByteChannel output = null; + try + { + if (canCreatePayload(capabilities, resolvedPath)) + { + output = Files.newByteChannel(resolvedPath, CREATE, WRITE); + } + else if (canWritePayload(capabilities, resolvedPath)) + { + tmpPath = Files.createTempFile(resolvedPath.getParent(), "temp-", ".tmp"); + output = Files.newByteChannel(tmpPath, WRITE); + } + } + catch (IOException ex) + { + // reject + } + return output; + } + + private void delete( + long traceId) + { + try + { + Files.delete(resolvedPath); + doAppWindow(traceId); + doAppBegin(traceId, null); + doAppEnd(traceId); + } + catch (IOException ex) + { + cleanup(traceId); + } + } + } + private void doBegin( MessageConsumer receiver, long originId, @@ -812,7 +1341,8 @@ private void doReset( long acknowledge, int maximum, long traceId, - long authorization) + long authorization, + Flyweight extension) { final ResetFW reset = resetRW.wrap(writeBuffer, 0, writeBuffer.capacity()) .originId(originId) @@ -823,6 +1353,7 @@ private void doReset( .maximum(maximum) .traceId(traceId) .authorization(authorization) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) .build(); receiver.accept(reset.typeId(), reset.buffer(), reset.offset(), reset.sizeof()); @@ -862,4 +1393,33 @@ private boolean canReadPayload( { return (capabilities & READ_PAYLOAD_MASK) != 0; } + + private boolean canWritePayload( + int capabilities, + Path path) + { + return (capabilities & WRITE_PAYLOAD_MASK) != 0 && Files.exists(path); + } + + private boolean canCreatePayload( + int capabilities, + Path path) + { + return (capabilities & CREATE_PAYLOAD_MASK) != 0 && Files.notExists(path); + } + + private boolean canDeletePayload( + int capabilities, + Path path) + { + return (capabilities & DELETE_PAYLOAD_MASK) != 0 && Files.exists(path); + } + + private boolean writeOperation( + int capabilities) + { + return (capabilities & CREATE_PAYLOAD_MASK) != 0 || + (capabilities & WRITE_PAYLOAD_MASK) != 0 || + (capabilities & DELETE_PAYLOAD_MASK) != 0; + } } diff --git a/runtime/binding-filesystem/src/test/java/io/aklivity/zilla/runtime/binding/filesystem/internal/stream/FileSystemServerIT.java b/runtime/binding-filesystem/src/test/java/io/aklivity/zilla/runtime/binding/filesystem/internal/stream/FileSystemServerIT.java index 2b20400986..f9e47e6cd7 100644 --- a/runtime/binding-filesystem/src/test/java/io/aklivity/zilla/runtime/binding/filesystem/internal/stream/FileSystemServerIT.java +++ b/runtime/binding-filesystem/src/test/java/io/aklivity/zilla/runtime/binding/filesystem/internal/stream/FileSystemServerIT.java @@ -137,9 +137,9 @@ public void shouldReadFilePayloadModifiedMultiClient() throws Exception @Test @Configuration("server.yaml") @Specification({ - "${app}/read.file.payload.etag.not.matched/client" + "${app}/read.file.payload.tag.not.matched/client" }) - public void shouldReadFilePayloadEtagNotMatched() throws Exception + public void shouldReadFilePayloadTagNotMatched() throws Exception { k3po.finish(); } @@ -252,4 +252,105 @@ public void shouldReceiveClientReadAbort() throws Exception { k3po.finish(); } + + @Test + @Configuration("server.yaml") + @Specification({ + "${app}/create.file.payload/client", + }) + public void shouldCreateFilePayloadOnly() throws Exception + { + Path targetDirectory = Paths.get("target/files").toAbsolutePath(); + Path indexFile = targetDirectory.resolve("index_write.html"); + + Files.deleteIfExists(indexFile); + + k3po.finish(); + } + + @Test + @Configuration("server.yaml") + @Specification({ + "${app}/write.file.payload.modified/client", + }) + public void shouldWriteFilePayloadModified() throws Exception + { + Path targetDirectory = Paths.get("target/files").toAbsolutePath(); + Path indexFile = targetDirectory.resolve("index_write.html"); + + Files.createDirectories(targetDirectory); + + Files.write(indexFile, """ + + Welcome + Hello, world + + """.getBytes()); + + k3po.finish(); + } + + @Test + @Configuration("server.yaml") + @Specification({ + "${app}/write.file.payload.modified.abort/client", + }) + public void shouldWriteFilePayloadModifiedAbort() throws Exception + { + k3po.finish(); + } + + @Test + @Configuration("server.yaml") + @Specification({ + "${app}/write.file.payload.interrupt/client", + }) + public void shouldWriteFilePayloadInterrupt() throws Exception + { + Path targetDirectory = Paths.get("target/files").toAbsolutePath(); + Path indexFile = targetDirectory.resolve("index_write.html"); + + Files.createDirectories(targetDirectory); + + Files.write(indexFile, """ + + Welcome + Hello, world + + """.getBytes()); + + k3po.finish(); + } + + @Test + @Configuration("server.yaml") + @Specification({ + "${app}/delete.file.payload/client", + }) + public void shouldDeleteFilePayload() throws Exception + { + Path targetDirectory = Paths.get("target/files").toAbsolutePath(); + Path indexFile = targetDirectory.resolve("error.html"); + + Files.createDirectories(targetDirectory); + + Files.write(indexFile, """ + + Welcome + Hello, world + + """.getBytes()); + + k3po.finish(); + } + + @Test + @Configuration("server.yaml") + @Specification({ + "${app}/delete.file.payload.failed/client", + }) + public void shouldRejectDeleteFilePayload() throws Exception + { + k3po.finish(); + } } diff --git a/runtime/binding-http-filesystem/src/main/java/io/aklivity/zilla/runtime/binding/http/filesystem/internal/config/HttpFileSystemWithResolver.java b/runtime/binding-http-filesystem/src/main/java/io/aklivity/zilla/runtime/binding/http/filesystem/internal/config/HttpFileSystemWithResolver.java index 41f518b026..e765c96b94 100644 --- a/runtime/binding-http-filesystem/src/main/java/io/aklivity/zilla/runtime/binding/http/filesystem/internal/config/HttpFileSystemWithResolver.java +++ b/runtime/binding-http-filesystem/src/main/java/io/aklivity/zilla/runtime/binding/http/filesystem/internal/config/HttpFileSystemWithResolver.java @@ -14,8 +14,11 @@ */ package io.aklivity.zilla.runtime.binding.http.filesystem.internal.config; +import static io.aklivity.zilla.runtime.binding.http.filesystem.internal.types.FileSystemCapabilities.CREATE_PAYLOAD; +import static io.aklivity.zilla.runtime.binding.http.filesystem.internal.types.FileSystemCapabilities.DELETE_PAYLOAD; import static io.aklivity.zilla.runtime.binding.http.filesystem.internal.types.FileSystemCapabilities.READ_EXTENSION; import static io.aklivity.zilla.runtime.binding.http.filesystem.internal.types.FileSystemCapabilities.READ_PAYLOAD; +import static io.aklivity.zilla.runtime.binding.http.filesystem.internal.types.FileSystemCapabilities.WRITE_PAYLOAD; import java.util.concurrent.TimeUnit; import java.util.function.Function; @@ -32,14 +35,21 @@ public final class HttpFileSystemWithResolver { public static final int HEADER_METHOD_MASK_HEAD = 1 << READ_EXTENSION.ordinal(); private static final int HEADER_METHOD_MASK_GET = 1 << READ_PAYLOAD.ordinal() | 1 << READ_EXTENSION.ordinal(); + public static final int HEADER_METHOD_MASK_POST = 1 << CREATE_PAYLOAD.ordinal(); + public static final int HEADER_METHOD_MASK_PUT = 1 << WRITE_PAYLOAD.ordinal(); + public static final int HEADER_METHOD_MASK_DELETE = 1 << DELETE_PAYLOAD.ordinal(); private static final Pattern PARAMS_PATTERN = Pattern.compile("\\$\\{params\\.([a-zA-Z_]+)\\}"); private static final Pattern PREFER_WAIT_PATTERN = Pattern.compile("wait=(\\d+)"); private static final String8FW HEADER_METHOD_NAME = new String8FW(":method"); private static final String8FW HEADER_IF_NONE_MATCH_NAME = new String8FW("if-none-match"); + private static final String8FW HEADER_IF_MATCH_NAME = new String8FW("if-match"); private static final String8FW HEADER_PREFER_NAME = new String8FW("prefer"); private static final String16FW HEADER_METHOD_VALUE_GET = new String16FW("GET"); private static final String16FW HEADER_METHOD_VALUE_HEAD = new String16FW("HEAD"); + private static final String16FW HEADER_METHOD_VALUE_POST = new String16FW("POST"); + private static final String16FW HEADER_METHOD_VALUE_PUT = new String16FW("PUT"); + private static final String16FW HEADER_METHOD_VALUE_DELETE = new String16FW("DELETE"); private final String16FW etagRO = new String16FW(); private final HttpFileSystemWithConfig with; @@ -73,6 +83,7 @@ public HttpFileSystemWithResult resolve( path0 = pathMatcher.replaceAll(replacer); } String16FW path = new String16FW(path0); + String16FW etag = new String16FW(""); HttpHeaderFW method = httpBeginEx.headers().matchFirst(h -> HEADER_METHOD_NAME.equals(h.name())); int capabilities = 0; @@ -86,14 +97,27 @@ else if (HEADER_METHOD_VALUE_GET.equals(method.value())) { capabilities = HEADER_METHOD_MASK_GET; } + else if (HEADER_METHOD_VALUE_POST.equals(method.value())) + { + capabilities = HEADER_METHOD_MASK_POST; + } + else if (HEADER_METHOD_VALUE_PUT.equals(method.value())) + { + capabilities = HEADER_METHOD_MASK_PUT; + } + else if (HEADER_METHOD_VALUE_DELETE.equals(method.value())) + { + capabilities = HEADER_METHOD_MASK_DELETE; + } } - HttpHeaderFW ifNotMatched = httpBeginEx.headers().matchFirst(h -> HEADER_IF_NONE_MATCH_NAME.equals(h.name())); - String16FW etag = new String16FW(""); - if (ifNotMatched != null) + HttpHeaderFW tag = httpBeginEx.headers().matchFirst(h -> + HEADER_IF_MATCH_NAME.equals(h.name()) || HEADER_IF_NONE_MATCH_NAME.equals(h.name())); + if (tag != null) { - String16FW value = ifNotMatched.value(); + String16FW value = tag.value(); etag = etagRO.wrap(value.buffer(), value.offset(), value.limit()); } + HttpHeaderFW prefer = httpBeginEx.headers().matchFirst(h -> HEADER_PREFER_NAME.equals(h.name())); int wait = 0; if (prefer != null) diff --git a/runtime/binding-http-filesystem/src/main/java/io/aklivity/zilla/runtime/binding/http/filesystem/internal/stream/HttpFileSystemProxyFactory.java b/runtime/binding-http-filesystem/src/main/java/io/aklivity/zilla/runtime/binding/http/filesystem/internal/stream/HttpFileSystemProxyFactory.java index 96711fb556..b10ad0dcc6 100644 --- a/runtime/binding-http-filesystem/src/main/java/io/aklivity/zilla/runtime/binding/http/filesystem/internal/stream/HttpFileSystemProxyFactory.java +++ b/runtime/binding-http-filesystem/src/main/java/io/aklivity/zilla/runtime/binding/http/filesystem/internal/stream/HttpFileSystemProxyFactory.java @@ -38,8 +38,12 @@ import io.aklivity.zilla.runtime.binding.http.filesystem.internal.types.stream.EndFW; import io.aklivity.zilla.runtime.binding.http.filesystem.internal.types.stream.ExtensionFW; import io.aklivity.zilla.runtime.binding.http.filesystem.internal.types.stream.FileSystemBeginExFW; +import io.aklivity.zilla.runtime.binding.http.filesystem.internal.types.stream.FileSystemError; +import io.aklivity.zilla.runtime.binding.http.filesystem.internal.types.stream.FileSystemErrorFW; +import io.aklivity.zilla.runtime.binding.http.filesystem.internal.types.stream.FileSystemResetExFW; import io.aklivity.zilla.runtime.binding.http.filesystem.internal.types.stream.FlushFW; import io.aklivity.zilla.runtime.binding.http.filesystem.internal.types.stream.HttpBeginExFW; +import io.aklivity.zilla.runtime.binding.http.filesystem.internal.types.stream.HttpResetExFW; import io.aklivity.zilla.runtime.binding.http.filesystem.internal.types.stream.ResetFW; import io.aklivity.zilla.runtime.binding.http.filesystem.internal.types.stream.WindowFW; import io.aklivity.zilla.runtime.engine.EngineContext; @@ -54,13 +58,22 @@ public final class HttpFileSystemProxyFactory implements HttpFileSystemStreamFac private static final String8FW HEADER_STATUS_NAME = new String8FW(":status"); private static final String16FW HEADER_STATUS_VALUE_200 = new String16FW("200"); + private static final String16FW HEADER_STATUS_VALUE_204 = new String16FW("204"); private static final String16FW HEADER_STATUS_VALUE_304 = new String16FW("304"); + private static final String16FW HEADER_STATUS_VALUE_404 = new String16FW("404"); + private static final String16FW HEADER_STATUS_VALUE_409 = new String16FW("409"); + private static final String16FW HEADER_STATUS_VALUE_428 = new String16FW("428"); + private static final String16FW HEADER_STATUS_VALUE_412 = new String16FW("412"); private static final String8FW HEADER_ETAG_NAME = new String8FW("etag"); private static final String8FW HEADER_CONTENT_TYPE_NAME = new String8FW("content-type"); private static final String8FW HEADER_CONTENT_LENGTH_NAME = new String8FW("content-length"); + private static final OctetsFW EMPTY_EXTENSION = new OctetsFW().wrap(new UnsafeBuffer(new byte[0]), 0, 0); private static final int READ_PAYLOAD_MASK = 1 << FileSystemCapabilities.READ_PAYLOAD.ordinal(); + private static final int WRITE_PAYLOAD_MASK = 1 << FileSystemCapabilities.WRITE_PAYLOAD.ordinal(); + private static final int CREATE_PAYLOAD_MASK = 1 << FileSystemCapabilities.CREATE_PAYLOAD.ordinal(); + private static final int DELETE_PAYLOAD_MASK = 1 << FileSystemCapabilities.DELETE_PAYLOAD.ordinal(); - private static final Predicate HEADER_METHOD_GET_OR_HEAD; + private static final Predicate SUPPORTED_HTTP_METHOD; static { @@ -76,9 +89,30 @@ public final class HttpFileSystemProxyFactory implements HttpFileSystemStreamFac .value("HEAD") .build(); + HttpHeaderFW headerMethodPost = new HttpHeaderFW.Builder() + .wrap(new UnsafeBuffer(new byte[512]), 0, 512) + .name(":method") + .value("POST") + .build(); + + HttpHeaderFW headerMethodPut = new HttpHeaderFW.Builder() + .wrap(new UnsafeBuffer(new byte[512]), 0, 512) + .name(":method") + .value("PUT") + .build(); + + HttpHeaderFW headerMethodDelete = new HttpHeaderFW.Builder() + .wrap(new UnsafeBuffer(new byte[512]), 0, 512) + .name(":method") + .value("DELETE") + .build(); + Predicate test = headerMethodGet::equals; test = test.or(headerMethodHead::equals); - HEADER_METHOD_GET_OR_HEAD = test; + test = test.or(headerMethodPost::equals); + test = test.or(headerMethodPut::equals); + test = test.or(headerMethodDelete::equals); + SUPPORTED_HTTP_METHOD = test; } private final OctetsFW emptyExRO = new OctetsFW().wrap(new UnsafeBuffer(0L, 0), 0, 0); @@ -104,8 +138,10 @@ public final class HttpFileSystemProxyFactory implements HttpFileSystemStreamFac private final HttpBeginExFW httpBeginExRO = new HttpBeginExFW(); private final FileSystemBeginExFW fsBeginExRO = new FileSystemBeginExFW(); + private final FileSystemResetExFW fsResetExRO = new FileSystemResetExFW(); private final HttpBeginExFW.Builder httpBeginExRW = new HttpBeginExFW.Builder(); + private final HttpResetExFW.Builder httpResetExRW = new HttpResetExFW.Builder(); private final FileSystemBeginExFW.Builder fsBeginExRW = new FileSystemBeginExFW.Builder(); private final MutableDirectBuffer writeBuffer; @@ -173,7 +209,7 @@ public MessageConsumer newStream( HttpFileSystemRouteConfig route = null; - if (binding != null && beginEx.headers().anyMatch(HEADER_METHOD_GET_OR_HEAD)) + if (binding != null && beginEx.headers().anyMatch(SUPPORTED_HTTP_METHOD)) { route = binding.resolve(authorization, beginEx); } @@ -300,6 +336,10 @@ private void onHttpData( final long acknowledge = data.acknowledge(); final long traceId = data.traceId(); final long authorization = data.authorization(); + final long budgetId = data.budgetId(); + final int reserved = data.reserved(); + final int flags = data.flags(); + final OctetsFW payload = data.payload(); assert acknowledge <= sequence; assert sequence >= initialSeq; @@ -308,8 +348,15 @@ private void onHttpData( assert initialAck <= initialSeq; - doHttpReset(traceId); - delegate.doFileSystemAbort(traceId, authorization); + if (delegate.createOrWritePayload(resolved.capabilities())) + { + delegate.doFileSystemData(traceId, authorization, budgetId, reserved, flags, payload); + } + else + { + doHttpReset(traceId, null); + delegate.doFileSystemAbort(traceId, authorization); + } } private void onHttpEnd( @@ -484,13 +531,46 @@ private void doHttpWindow( } private void doHttpReset( - long traceId) + long traceId, + FileSystemErrorFW error) { if (!HttpFileSystemState.initialClosed(state)) { state = HttpFileSystemState.closeInitial(state); - doReset(http, originId, routedId, initialId, initialSeq, initialAck, initialMax, traceId); + String16FW httpStatus; + + if (error != null) + { + FileSystemError errorMessage = error.get(); + switch (errorMessage) + { + case FILE_ALREADY_EXISTS: + httpStatus = HEADER_STATUS_VALUE_409; + break; + case FILE_MODIFIED: + httpStatus = HEADER_STATUS_VALUE_412; + break; + case FILE_TAG_MISSING: + httpStatus = HEADER_STATUS_VALUE_428; + break; + default: + httpStatus = HEADER_STATUS_VALUE_404; + break; + } + } + else + { + httpStatus = HEADER_STATUS_VALUE_404; + } + + HttpResetExFW resetEx = httpResetExRW.wrap(extBuffer, 0, extBuffer.capacity()) + .typeId(httpTypeId) + .headersItem(h -> h.name(HEADER_STATUS_NAME).value(httpStatus)) + .headersItem(h -> h.name(HEADER_CONTENT_LENGTH_NAME).value("0")) + .build(); + + doReset(http, originId, routedId, initialId, initialSeq, initialAck, initialMax, traceId, resetEx); } } } @@ -542,6 +622,18 @@ private void doFileSystemBegin( traceId, authorization, affinity, resolved); } + private void doFileSystemData( + long traceId, + long authorization, + long budgetId, + int reserved, + int flags, + Flyweight payload) + { + doData(filesystem, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, flags, reserved, payload); + } + private void doFileSystemEnd( long traceId, long sequence, @@ -645,13 +737,31 @@ private void onFileSystemBegin( { final HttpBeginExFW.Builder httpBeginExBuilder = httpBeginExRW.wrap(extBuffer, 0, extBuffer.capacity()) - .typeId(httpTypeId) - .headersItem(h -> h.name(HEADER_STATUS_NAME).value(getStatus(fsBeginEx))) + .typeId(httpTypeId); + final boolean validTag = tag != null ? tag.length() != -1 && tag.asString() != null : false; + + int capabilities = fsBeginEx.capabilities(); + if (createOrWritePayload(capabilities) && validTag) + { + httpBeginExBuilder.headersItem(h -> h.name(HEADER_STATUS_NAME).value(HEADER_STATUS_VALUE_204)) + .headersItem(h -> h.name(HEADER_ETAG_NAME).value(tag)) + .headersItem(h -> h.name(HEADER_CONTENT_LENGTH_NAME).value("0")); + } + else if ((capabilities & DELETE_PAYLOAD_MASK) != 0) + { + httpBeginExBuilder.headersItem(h -> h.name(HEADER_STATUS_NAME).value(HEADER_STATUS_VALUE_204)) + .headersItem(h -> h.name(HEADER_CONTENT_LENGTH_NAME).value("0")); + } + else + { + httpBeginExBuilder.headersItem(h -> h.name(HEADER_STATUS_NAME).value(getStatus(fsBeginEx))) .headersItem(h -> h.name(HEADER_CONTENT_TYPE_NAME).value(type)) .headersItem(h -> h.name(HEADER_CONTENT_LENGTH_NAME).value(length)); - if (tag.length() != -1 && tag.asString() != null) - { - httpBeginExBuilder.headersItem(h -> h.name(HEADER_ETAG_NAME).value(tag)); + + if (validTag) + { + httpBeginExBuilder.headersItem(h -> h.name(HEADER_ETAG_NAME).value(tag)); + } } httpBeginEx = httpBeginExBuilder.build(); } @@ -675,6 +785,12 @@ private boolean canReadPayload( return (capabilities & READ_PAYLOAD_MASK) != 0; } + private boolean createOrWritePayload( + int capabilities) + { + return (capabilities & CREATE_PAYLOAD_MASK) != 0 || (capabilities & WRITE_PAYLOAD_MASK) != 0; + } + private void onFileSystemData( DataFW data) { @@ -794,6 +910,9 @@ private void onFileSystemReset( final long sequence = reset.sequence(); final long acknowledge = reset.acknowledge(); final long traceId = reset.traceId(); + final OctetsFW extension = reset.extension(); + final FileSystemResetExFW resetEx = extension.get(fsResetExRO::tryWrap); + final FileSystemErrorFW error = resetEx != null ? resetEx.error() : null; assert acknowledge <= sequence; assert acknowledge >= delegate.initialAck; @@ -802,7 +921,7 @@ private void onFileSystemReset( assert delegate.initialAck <= delegate.initialSeq; - delegate.doHttpReset(traceId); + delegate.doHttpReset(traceId, error); } private void doFileSystemReset( @@ -813,7 +932,7 @@ private void doFileSystemReset( state = HttpFileSystemState.closeReply(state); doReset(filesystem, originId, routedId, replyId, replySeq, replyAck, replyMax, - traceId); + traceId, EMPTY_EXTENSION); } } @@ -1055,7 +1174,8 @@ private void doReset( long sequence, long acknowledge, int maximum, - long traceId) + long traceId, + Flyweight extension) { final ResetFW reset = resetRW.wrap(writeBuffer, 0, writeBuffer.capacity()) .originId(originId) @@ -1065,6 +1185,7 @@ private void doReset( .acknowledge(acknowledge) .maximum(maximum) .traceId(traceId) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) .build(); sender.accept(reset.typeId(), reset.buffer(), reset.offset(), reset.sizeof()); diff --git a/runtime/binding-http-filesystem/src/test/java/io/aklivity/zilla/runtime/binding/http/filesystem/internal/stream/HttpFileSystemProxyIT.java b/runtime/binding-http-filesystem/src/test/java/io/aklivity/zilla/runtime/binding/http/filesystem/internal/stream/HttpFileSystemProxyIT.java index a6eb5bea95..ae8ca7ba1d 100644 --- a/runtime/binding-http-filesystem/src/test/java/io/aklivity/zilla/runtime/binding/http/filesystem/internal/stream/HttpFileSystemProxyIT.java +++ b/runtime/binding-http-filesystem/src/test/java/io/aklivity/zilla/runtime/binding/http/filesystem/internal/stream/HttpFileSystemProxyIT.java @@ -127,15 +127,6 @@ public void shouldRejectClientWithNoBinding() throws Exception k3po.finish(); } - @Test - @Configuration("proxy.with.path.yaml") - @Specification({ - "${http}/client.write.file.rejected/client"}) - public void shouldRejectClientWriteFile() throws Exception - { - k3po.finish(); - } - @Test @Specification({ "${http}/client.rejected/client"}) @@ -203,4 +194,64 @@ public void shouldReceiveClientSentAbort() throws Exception { k3po.finish(); } + + @Test + @Configuration("proxy.with.path.yaml") + @Specification({ + "${http}/client.create.file/client", + "${filesystem}/client.create.file/server"}) + public void shouldReceiveClientCreateFile() throws Exception + { + k3po.finish(); + } + + @Test + @Configuration("proxy.with.path.yaml") + @Specification({ + "${http}/client.create.existing.file.failed/client", + "${filesystem}/client.create.existing.file.failed/server"}) + public void shouldRejectClientCreateExistingFileFailed() throws Exception + { + k3po.finish(); + } + + @Test + @Configuration("proxy.with.path.yaml") + @Specification({ + "${http}/client.write.file/client", + "${filesystem}/client.write.file/server"}) + public void shouldReceiveClientWriteFile() throws Exception + { + k3po.finish(); + } + + @Test + @Configuration("proxy.with.path.yaml") + @Specification({ + "${http}/client.write.file.failed/client", + "${filesystem}/client.write.file.failed/server"}) + public void shouldRejectClientWriteFileFailed() throws Exception + { + k3po.finish(); + } + + @Test + @Configuration("proxy.with.path.yaml") + @Specification({ + "${http}/client.delete.file/client", + "${filesystem}/client.delete.file/server"}) + public void shouldReceiveClientDeleteFile() throws Exception + { + k3po.finish(); + } + + @Test + @Configuration("proxy.with.path.yaml") + @Specification({ + "${http}/client.delete.non.existent.file/client", + "${filesystem}/client.delete.non.existent.file/server"}) + public void shouldRejectClientDeleteNonExistentFile() throws Exception + { + k3po.finish(); + } } 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 df9f1e66b2..a555b65e25 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 @@ -2946,7 +2946,13 @@ private void onRequestReset( else { responseState = HttpExchangeState.CLOSED; - doEncodeHeaders(this, traceId, authorization, 0L, headers404); + + OctetsFW extension = reset.extension(); + HttpResetExFW httpResetEx = extension.get(resetExRO::tryWrap); + Array32FW headers = httpResetEx != null && !httpResetEx.headers().isEmpty() ? + httpResetEx.headers() : headers404; + + doEncodeHeaders(this, traceId, authorization, 0L, headers); } requestState = HttpExchangeState.CLOSED; diff --git a/specs/binding-filesystem.spec/pom.xml b/specs/binding-filesystem.spec/pom.xml index 32ed1f9cb4..d95e29fbcc 100644 --- a/specs/binding-filesystem.spec/pom.xml +++ b/specs/binding-filesystem.spec/pom.xml @@ -24,7 +24,7 @@ - 1.00 + 0.99 0 diff --git a/specs/binding-filesystem.spec/src/main/java/io/aklivity/zilla/specs/binding/filesystem/internal/FileSystemFunctions.java b/specs/binding-filesystem.spec/src/main/java/io/aklivity/zilla/specs/binding/filesystem/internal/FileSystemFunctions.java index a3aefda2bb..0aefa37ac4 100644 --- a/specs/binding-filesystem.spec/src/main/java/io/aklivity/zilla/specs/binding/filesystem/internal/FileSystemFunctions.java +++ b/specs/binding-filesystem.spec/src/main/java/io/aklivity/zilla/specs/binding/filesystem/internal/FileSystemFunctions.java @@ -26,6 +26,9 @@ import io.aklivity.k3po.runtime.lang.el.spi.FunctionMapperSpi; import io.aklivity.zilla.specs.binding.filesystem.internal.types.FileSystemCapabilities; import io.aklivity.zilla.specs.binding.filesystem.internal.types.stream.FileSystemBeginExFW; +import io.aklivity.zilla.specs.binding.filesystem.internal.types.stream.FileSystemError; +import io.aklivity.zilla.specs.binding.filesystem.internal.types.stream.FileSystemErrorFW; +import io.aklivity.zilla.specs.binding.filesystem.internal.types.stream.FileSystemResetExFW; public final class FileSystemFunctions { @@ -35,12 +38,24 @@ public static FileSystemBeginExBuilder beginEx() return new FileSystemBeginExBuilder(); } + @Function + public static FileSystemResetExBuilder resetEx() + { + return new FileSystemResetExBuilder(); + } + @Function public static FileSystemBeginExMatcherBuilder matchBeginEx() { return new FileSystemBeginExMatcherBuilder(); } + @Function + public static FileSystemResetExMatcherBuilder matchResetEx() + { + return new FileSystemResetExMatcherBuilder(); + } + public static final class FileSystemBeginExBuilder { private final FileSystemBeginExFW.Builder beginExRW; @@ -256,6 +271,105 @@ private boolean matchTimeout( } } + public static final class FileSystemResetExBuilder + { + private final FileSystemResetExFW.Builder resetExRW; + private final FileSystemErrorFW.Builder errorExRW; + + private FileSystemResetExBuilder() + { + MutableDirectBuffer writeBuffer = new UnsafeBuffer(new byte[1024 * 8]); + this.resetExRW = new FileSystemResetExFW.Builder().wrap(writeBuffer, 0, writeBuffer.capacity()); + MutableDirectBuffer errorBuffer = new UnsafeBuffer(new byte[1]); + this.errorExRW = new FileSystemErrorFW.Builder().wrap(errorBuffer, 0, errorBuffer.capacity()); + } + + public FileSystemResetExBuilder typeId( + int typeId) + { + resetExRW.typeId(typeId); + return this; + } + + public FileSystemResetExBuilder error( + String error) + { + resetExRW.error(errorExRW.set(FileSystemError.valueOf(error)).build()); + return this; + } + + public byte[] build() + { + final FileSystemResetExFW resetEx = resetExRW.build(); + final byte[] array = new byte[resetEx.sizeof()]; + resetEx.buffer().getBytes(resetEx.offset(), array); + return array; + } + } + + public static final class FileSystemResetExMatcherBuilder + { + private final DirectBuffer bufferRO = new UnsafeBuffer(); + + private final FileSystemResetExFW resetExRO = new FileSystemResetExFW(); + + private Integer typeId; + private String error; + + public FileSystemResetExMatcherBuilder typeId( + int typeId) + { + this.typeId = typeId; + return this; + } + + public FileSystemResetExMatcherBuilder error( + String error) + { + this.error = error; + return this; + } + + public BytesMatcher build() + { + return typeId != null ? this::match : buf -> null; + } + + private FileSystemResetExFW match( + ByteBuffer byteBuf) throws Exception + { + if (!byteBuf.hasRemaining()) + { + return null; + } + + bufferRO.wrap(byteBuf); + final FileSystemResetExFW resetEx = resetExRO.tryWrap(bufferRO, byteBuf.position(), byteBuf.capacity()); + + if (resetEx != null && + matchTypeId(resetEx) && + matchError(resetEx)) + { + byteBuf.position(byteBuf.position() + resetEx.sizeof()); + return resetEx; + } + + throw new Exception(resetEx.toString()); + } + + private boolean matchTypeId( + FileSystemResetExFW resetEx) + { + return typeId == resetEx.typeId(); + } + + private boolean matchError( + FileSystemResetExFW resetEx) + { + return error == null || error.equals(resetEx.error().get().name()); + } + } + public static class Mapper extends FunctionMapperSpi.Reflective { public Mapper() diff --git a/specs/binding-filesystem.spec/src/main/resources/META-INF/zilla/filesystem.idl b/specs/binding-filesystem.spec/src/main/resources/META-INF/zilla/filesystem.idl index a63c84e4ee..92552c2e5a 100644 --- a/specs/binding-filesystem.spec/src/main/resources/META-INF/zilla/filesystem.idl +++ b/specs/binding-filesystem.spec/src/main/resources/META-INF/zilla/filesystem.idl @@ -18,11 +18,22 @@ scope filesystem { READ_PAYLOAD, READ_EXTENSION, - READ_CHANGES + READ_CHANGES, + CREATE_PAYLOAD, + WRITE_PAYLOAD, + DELETE_PAYLOAD } scope stream { + enum FileSystemError + { + FILE_NOT_FOUND, + FILE_ALREADY_EXISTS, + FILE_MODIFIED, + FILE_TAG_MISSING + } + struct FileSystemBeginEx extends core::stream::Extension { int32 capabilities = 3; @@ -32,5 +43,10 @@ scope filesystem string16 tag = null; int64 timeout = 0; } + + struct FileSystemResetEx extends core::stream::Extension + { + FileSystemError error; + } } } diff --git a/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/create.file.payload/client.rpt b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/create.file.payload/client.rpt new file mode 100644 index 0000000000..d89504515a --- /dev/null +++ b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/create.file.payload/client.rpt @@ -0,0 +1,44 @@ +# +# Copyright 2021-2024 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +connect "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${filesystem:beginEx() + .typeId(zilla:id("filesystem")) + .capabilities("CREATE_PAYLOAD") + .path("index_write.html") + .type("text/html") + .payloadSize(77) + .build()} +connected + +write "\n" + "Welcome\n" + "Hello, world\n" + "\n" +write flush + +write close + +read zilla:begin.ext ${filesystem:matchBeginEx() + .typeId(zilla:id("filesystem")) + .capabilities("CREATE_PAYLOAD") + .path("index_write.html") + .tag("f2c912a30f38a124c3249f8e802e0d90") + .build()} + +read closed diff --git a/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/create.file.payload/server.rpt b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/create.file.payload/server.rpt new file mode 100644 index 0000000000..dcde111481 --- /dev/null +++ b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/create.file.payload/server.rpt @@ -0,0 +1,46 @@ +# +# Copyright 2021-2024 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. +# + +accept "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${filesystem:matchBeginEx() + .typeId(zilla:id("filesystem")) + .capabilities("CREATE_PAYLOAD") + .path("index_write.html") + .type("text/html") + .payloadSize(77) + .build()} +connected + +read "\n" + "Welcome\n" + "Hello, world\n" + "\n" + +read closed + +write zilla:begin.ext ${filesystem:beginEx() + .typeId(zilla:id("filesystem")) + .capabilities("CREATE_PAYLOAD") + .path("index_write.html") + .tag("f2c912a30f38a124c3249f8e802e0d90") + .build()} + +write flush + +write close diff --git a/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/delete.file.payload.failed/client.rpt b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/delete.file.payload.failed/client.rpt new file mode 100644 index 0000000000..bdcba4d477 --- /dev/null +++ b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/delete.file.payload.failed/client.rpt @@ -0,0 +1,25 @@ +# +# Copyright 2021-2024 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +connect "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${filesystem:beginEx() + .typeId(zilla:id("filesystem")) + .capabilities("DELETE_PAYLOAD") + .path("error.html") + .build()} +connect aborted diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.write.file.rejected/server.rpt b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/delete.file.payload.failed/server.rpt similarity index 95% rename from specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.write.file.rejected/server.rpt rename to specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/delete.file.payload.failed/server.rpt index 2d35ef0843..67d4f2732e 100644 --- a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.write.file.rejected/server.rpt +++ b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/delete.file.payload.failed/server.rpt @@ -13,8 +13,7 @@ # specific language governing permissions and limitations under the License. # -accept "zilla://streams/http0" +accept "zilla://streams/app0" option zilla:window 8192 option zilla:transmission "half-duplex" - rejected diff --git a/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/delete.file.payload/client.rpt b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/delete.file.payload/client.rpt new file mode 100644 index 0000000000..cb15ded6c1 --- /dev/null +++ b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/delete.file.payload/client.rpt @@ -0,0 +1,35 @@ +# +# Copyright 2021-2024 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +connect "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${filesystem:beginEx() + .typeId(zilla:id("filesystem")) + .capabilities("DELETE_PAYLOAD") + .path("error.html") + .build()} +connected + +write close + +read zilla:begin.ext ${filesystem:matchBeginEx() + .typeId(zilla:id("filesystem")) + .capabilities("DELETE_PAYLOAD") + .path("error.html") + .build()} + +read closed diff --git a/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/delete.file.payload/server.rpt b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/delete.file.payload/server.rpt new file mode 100644 index 0000000000..4e68418642 --- /dev/null +++ b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/delete.file.payload/server.rpt @@ -0,0 +1,38 @@ +# +# Copyright 2021-2024 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. +# + +accept "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${filesystem:matchBeginEx() + .typeId(zilla:id("filesystem")) + .capabilities("DELETE_PAYLOAD") + .path("error.html") + .build()} +connected + +read closed + +write zilla:begin.ext ${filesystem:beginEx() + .typeId(zilla:id("filesystem")) + .capabilities("DELETE_PAYLOAD") + .path("error.html") + .build()} + +write flush + +write close diff --git a/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/read.file.payload.etag.not.matched/client.rpt b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/read.file.payload.tag.not.matched/client.rpt similarity index 100% rename from specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/read.file.payload.etag.not.matched/client.rpt rename to specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/read.file.payload.tag.not.matched/client.rpt diff --git a/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/read.file.payload.etag.not.matched/server.rpt b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/read.file.payload.tag.not.matched/server.rpt similarity index 100% rename from specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/read.file.payload.etag.not.matched/server.rpt rename to specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/read.file.payload.tag.not.matched/server.rpt diff --git a/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/write.file.payload.interrupt/client.rpt b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/write.file.payload.interrupt/client.rpt new file mode 100644 index 0000000000..4ec9492625 --- /dev/null +++ b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/write.file.payload.interrupt/client.rpt @@ -0,0 +1,40 @@ +# +# Copyright 2021-2024 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +connect "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${filesystem:beginEx() + .typeId(zilla:id("filesystem")) + .capabilities("WRITE_PAYLOAD") + .path("index_write.html") + .type("text/html") + .payloadSize(77) + .tag("f2c912a30f38a124c3249f8e802e0d90") + .build()} +connected + +write option zilla:flags "init" +write "\n" + "Welcome\n" +write flush + +write option zilla:flags "none" +write "Hello, user\n" + "\n" +write flush + +write abort diff --git a/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/write.file.payload.interrupt/server.rpt b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/write.file.payload.interrupt/server.rpt new file mode 100644 index 0000000000..bb4fc7a573 --- /dev/null +++ b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/write.file.payload.interrupt/server.rpt @@ -0,0 +1,39 @@ +# +# Copyright 2021-2024 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. +# + +accept "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${filesystem:matchBeginEx() + .typeId(zilla:id("filesystem")) + .capabilities("WRITE_PAYLOAD") + .path("index_write.html") + .type("text/html") + .payloadSize(77) + .tag("f2c912a30f38a124c3249f8e802e0d90") + .build()} +connected + +read option zilla:flags "init" +read "\n" + "Welcome\n" + +read option zilla:flags "none" +read "Hello, user\n" + "\n" + +read aborted diff --git a/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/write.file.payload.modified.abort/client.rpt b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/write.file.payload.modified.abort/client.rpt new file mode 100644 index 0000000000..5629cebb52 --- /dev/null +++ b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/write.file.payload.modified.abort/client.rpt @@ -0,0 +1,29 @@ +# +# Copyright 2021-2024 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +connect "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${filesystem:beginEx() + .typeId(zilla:id("filesystem")) + .capabilities("WRITE_PAYLOAD") + .path("index_write.html") + .type("text/html") + .payloadSize(77) + .tag("INIT") + .build()} + +connect aborted diff --git a/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/write.file.payload.modified.abort/server.rpt b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/write.file.payload.modified.abort/server.rpt new file mode 100644 index 0000000000..67d4f2732e --- /dev/null +++ b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/write.file.payload.modified.abort/server.rpt @@ -0,0 +1,19 @@ +# +# Copyright 2021-2024 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. +# + +accept "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +rejected diff --git a/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/write.file.payload.modified/client.rpt b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/write.file.payload.modified/client.rpt new file mode 100644 index 0000000000..23def1fa23 --- /dev/null +++ b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/write.file.payload.modified/client.rpt @@ -0,0 +1,45 @@ +# +# Copyright 2021-2024 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +connect "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${filesystem:beginEx() + .typeId(zilla:id("filesystem")) + .capabilities("WRITE_PAYLOAD") + .path("index_write.html") + .type("text/html") + .payloadSize(77) + .tag("f2c912a30f38a124c3249f8e802e0d90") + .build()} +connected + +write "\n" + "Welcome\n" + "Hello, Zilla\n" + "\n" +write flush + +write close + +read zilla:begin.ext ${filesystem:matchBeginEx() + .typeId(zilla:id("filesystem")) + .capabilities("WRITE_PAYLOAD") + .path("index_write.html") + .tag("c3ce2fda552e43985939a773852f45d1") + .build()} + +read closed diff --git a/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/write.file.payload.modified/server.rpt b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/write.file.payload.modified/server.rpt new file mode 100644 index 0000000000..d849c8305e --- /dev/null +++ b/specs/binding-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/filesystem/streams/application/write.file.payload.modified/server.rpt @@ -0,0 +1,47 @@ +# +# Copyright 2021-2024 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. +# + +accept "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${filesystem:matchBeginEx() + .typeId(zilla:id("filesystem")) + .capabilities("WRITE_PAYLOAD") + .path("index_write.html") + .type("text/html") + .payloadSize(77) + .tag("f2c912a30f38a124c3249f8e802e0d90") + .build()} +connected + +read "\n" + "Welcome\n" + "Hello, Zilla\n" + "\n" + +read closed + +write zilla:begin.ext ${filesystem:beginEx() + .typeId(zilla:id("filesystem")) + .capabilities("WRITE_PAYLOAD") + .path("index_write.html") + .tag("c3ce2fda552e43985939a773852f45d1") + .build()} + +write flush + +write close diff --git a/specs/binding-filesystem.spec/src/test/java/io/aklivity/zilla/specs/binding/filesystem/internal/FileSystemFunctionsTest.java b/specs/binding-filesystem.spec/src/test/java/io/aklivity/zilla/specs/binding/filesystem/internal/FileSystemFunctionsTest.java index aa194bc977..a983bdf236 100644 --- a/specs/binding-filesystem.spec/src/test/java/io/aklivity/zilla/specs/binding/filesystem/internal/FileSystemFunctionsTest.java +++ b/specs/binding-filesystem.spec/src/test/java/io/aklivity/zilla/specs/binding/filesystem/internal/FileSystemFunctionsTest.java @@ -34,6 +34,9 @@ import io.aklivity.k3po.runtime.lang.el.BytesMatcher; import io.aklivity.k3po.runtime.lang.internal.el.ExpressionContext; import io.aklivity.zilla.specs.binding.filesystem.internal.types.stream.FileSystemBeginExFW; +import io.aklivity.zilla.specs.binding.filesystem.internal.types.stream.FileSystemError; +import io.aklivity.zilla.specs.binding.filesystem.internal.types.stream.FileSystemErrorFW; +import io.aklivity.zilla.specs.binding.filesystem.internal.types.stream.FileSystemResetExFW; public class FileSystemFunctionsTest { @@ -354,4 +357,89 @@ public void shouldNotMatchBeginExtensionWhenBufferOverflow() throws Exception assertNull(matcher.match(byteBuf)); } + + @Test + public void shouldGenerateResetExtension() + { + byte[] build = FileSystemFunctions.resetEx() + .typeId(0x01) + .error("FILE_MODIFIED") + .build(); + + DirectBuffer buffer = new UnsafeBuffer(build); + FileSystemResetExFW resetEx = new FileSystemResetExFW().wrap(buffer, 0, buffer.capacity()); + + assertEquals(0x01, resetEx.typeId()); + assertEquals(FileSystemError.FILE_MODIFIED, resetEx.error().get()); + } + + @Test + public void shouldMatchResetExtension() throws Exception + { + BytesMatcher matcher = FileSystemFunctions.matchResetEx() + .typeId(0x01) + .error("FILE_MODIFIED") + .build(); + + ByteBuffer byteBuf = ByteBuffer.allocate(1024); + + new FileSystemResetExFW.Builder().wrap(new UnsafeBuffer(byteBuf), 0, byteBuf.capacity()) + .typeId(0x01) + .error(new FileSystemErrorFW.Builder().wrap(new UnsafeBuffer(new byte[1]), 0, 1) + .set(FileSystemError.FILE_MODIFIED).build()) + .build(); + + assertNotNull(matcher.match(byteBuf)); + } + + @Test(expected = Exception.class) + public void shouldNotMatchResetExtensionWhenErrorDiffers() throws Exception + { + BytesMatcher matcher = FileSystemFunctions.matchResetEx() + .typeId(0x01) + .error("FILE_MODIFIED") + .build(); + + ByteBuffer byteBuf = ByteBuffer.allocate(1024); + + new FileSystemResetExFW.Builder().wrap(new UnsafeBuffer(byteBuf), 0, byteBuf.capacity()) + .typeId(0x01) + .error(new FileSystemErrorFW.Builder().wrap(new UnsafeBuffer(new byte[1]), 0, 1) + .set(FileSystemError.FILE_NOT_FOUND).build()) + .build(); + + assertNotNull(matcher.match(byteBuf)); + } + + @Test(expected = Exception.class) + public void shouldNotMatchResetExtensionWhenTypeIdDiffers() throws Exception + { + BytesMatcher matcher = FileSystemFunctions.matchResetEx() + .typeId(0x01) + .error("FILE_MODIFIED") + .build(); + + ByteBuffer byteBuf = ByteBuffer.allocate(1024); + + new FileSystemResetExFW.Builder().wrap(new UnsafeBuffer(byteBuf), 0, byteBuf.capacity()) + .typeId(0x02) + .error(new FileSystemErrorFW.Builder().wrap(new UnsafeBuffer(new byte[1]), 0, 1) + .set(FileSystemError.FILE_NOT_FOUND).build()) + .build(); + + assertNotNull(matcher.match(byteBuf)); + } + + @Test + public void shouldNotMatchResetExtensionWhenBufferOverflow() throws Exception + { + BytesMatcher matcher = FileSystemFunctions.matchResetEx() + .typeId(0x01) + .error("FILE_MODIFIED") + .build(); + + ByteBuffer byteBuf = ByteBuffer.allocate(0); + + assertNull(matcher.match(byteBuf)); + } } diff --git a/specs/binding-filesystem.spec/src/test/java/io/aklivity/zilla/specs/binding/filesystem/streams/application/FileSystemIT.java b/specs/binding-filesystem.spec/src/test/java/io/aklivity/zilla/specs/binding/filesystem/streams/application/FileSystemIT.java index cec8c92bca..93e9443973 100644 --- a/specs/binding-filesystem.spec/src/test/java/io/aklivity/zilla/specs/binding/filesystem/streams/application/FileSystemIT.java +++ b/specs/binding-filesystem.spec/src/test/java/io/aklivity/zilla/specs/binding/filesystem/streams/application/FileSystemIT.java @@ -98,10 +98,10 @@ public void shouldReadFilePayloadModifiedMultiClient() throws Exception @Test @Specification({ - "${app}/read.file.payload.etag.not.matched/client", - "${app}/read.file.payload.etag.not.matched/server" + "${app}/read.file.payload.tag.not.matched/client", + "${app}/read.file.payload.tag.not.matched/server" }) - public void shouldReadFilePayloadEtagNotMatched() throws Exception + public void shouldReadFilePayloadTagNotMatched() throws Exception { k3po.finish(); } @@ -165,4 +165,64 @@ public void shouldReceiveClientReadAbort() throws Exception { k3po.finish(); } + + @Test + @Specification({ + "${app}/create.file.payload/client", + "${app}/create.file.payload/server", + }) + public void shouldCreateFilePayloadOnly() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${app}/write.file.payload.modified/client", + "${app}/write.file.payload.modified/server", + }) + public void shouldWriteFilePayloadModified() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${app}/write.file.payload.modified.abort/client", + "${app}/write.file.payload.modified.abort/server", + }) + public void shouldWriteFilePayloadModifiedAbort() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${app}/write.file.payload.interrupt/client", + "${app}/write.file.payload.interrupt/server", + }) + public void shouldWriteFilePayloadInterrupt() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${app}/delete.file.payload/client", + "${app}/delete.file.payload/server", + }) + public void shouldDeleteFilePayloadOnly() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${app}/delete.file.payload.failed/client", + "${app}/delete.file.payload.failed/server", + }) + public void shouldDeleteFilePayloadFailed() throws Exception + { + k3po.finish(); + } } diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.create.existing.file.failed/client.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.create.existing.file.failed/client.rpt new file mode 100644 index 0000000000..00ecd03acf --- /dev/null +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.create.existing.file.failed/client.rpt @@ -0,0 +1,32 @@ +# +# Copyright 2021-2024 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +connect "zilla://streams/filesystem0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${filesystem:beginEx() + .typeId(zilla:id("filesystem")) + .capabilities("CREATE_PAYLOAD") + .path("index.html") + .build()} +connected + +read zilla:reset.ext ${filesystem:resetEx() + .typeId(zilla:id("filesystem")) + .error("FILE_ALREADY_EXISTS") + .build()} + +write aborted diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.create.existing.file.failed/server.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.create.existing.file.failed/server.rpt new file mode 100644 index 0000000000..788bbdfa6f --- /dev/null +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.create.existing.file.failed/server.rpt @@ -0,0 +1,33 @@ +# +# Copyright 2021-2024 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. +# + +accept "zilla://streams/filesystem0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${filesystem:matchBeginEx() + .typeId(zilla:id("filesystem")) + .capabilities("CREATE_PAYLOAD") + .path("index.html") + .build()} +connected + +write zilla:reset.ext ${filesystem:resetEx() + .typeId(zilla:id("filesystem")) + .error("FILE_ALREADY_EXISTS") + .build()} + +read abort diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.create.file/client.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.create.file/client.rpt new file mode 100644 index 0000000000..c1010cdb66 --- /dev/null +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.create.file/client.rpt @@ -0,0 +1,42 @@ +# +# Copyright 2021-2024 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +connect "zilla://streams/filesystem0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${filesystem:beginEx() + .typeId(zilla:id("filesystem")) + .capabilities("CREATE_PAYLOAD") + .path("index.html") + .build()} +connected + +write "\n" + "Welcome\n" + "Hello, world\n" + "\n" +write flush + +write close + +read zilla:begin.ext ${filesystem:matchBeginEx() + .typeId(zilla:id("filesystem")) + .capabilities("CREATE_PAYLOAD") + .path("index.html") + .tag("c7183509522eb56e5cf927a3b2e8c15a") + .build()} + +read closed diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.create.file/server.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.create.file/server.rpt new file mode 100644 index 0000000000..097d446b9d --- /dev/null +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.create.file/server.rpt @@ -0,0 +1,43 @@ +# +# Copyright 2021-2024 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. +# + +accept "zilla://streams/filesystem0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${filesystem:matchBeginEx() + .typeId(zilla:id("filesystem")) + .capabilities("CREATE_PAYLOAD") + .path("index.html") + .build()} +connected + +read "\n" + "Welcome\n" + "Hello, world\n" + "\n" + +read closed + +write zilla:begin.ext ${filesystem:beginEx() + .typeId(zilla:id("filesystem")) + .capabilities("CREATE_PAYLOAD") + .path("index.html") + .tag("c7183509522eb56e5cf927a3b2e8c15a") + .build()} +write flush + +write close diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.delete.file/client.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.delete.file/client.rpt new file mode 100644 index 0000000000..29c8d68272 --- /dev/null +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.delete.file/client.rpt @@ -0,0 +1,35 @@ +# +# Copyright 2021-2024 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +connect "zilla://streams/filesystem0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${filesystem:beginEx() + .typeId(zilla:id("filesystem")) + .capabilities("DELETE_PAYLOAD") + .path("index.html") + .build()} +connected + +write close + +read zilla:begin.ext ${filesystem:matchBeginEx() + .typeId(zilla:id("filesystem")) + .capabilities("DELETE_PAYLOAD") + .path("index.html") + .build()} + +read closed diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.delete.file/server.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.delete.file/server.rpt new file mode 100644 index 0000000000..b76b292bcd --- /dev/null +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.delete.file/server.rpt @@ -0,0 +1,37 @@ +# +# Copyright 2021-2024 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. +# + +accept "zilla://streams/filesystem0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${filesystem:matchBeginEx() + .typeId(zilla:id("filesystem")) + .capabilities("DELETE_PAYLOAD") + .path("index.html") + .build()} +connected + +read closed + +write zilla:begin.ext ${filesystem:beginEx() + .typeId(zilla:id("filesystem")) + .capabilities("DELETE_PAYLOAD") + .path("index.html") + .build()} +write flush + +write close diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.delete.non.existent.file/client.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.delete.non.existent.file/client.rpt new file mode 100644 index 0000000000..6f801aeeb9 --- /dev/null +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.delete.non.existent.file/client.rpt @@ -0,0 +1,32 @@ +# +# Copyright 2021-2024 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +connect "zilla://streams/filesystem0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${filesystem:beginEx() + .typeId(zilla:id("filesystem")) + .capabilities("DELETE_PAYLOAD") + .path("index.html") + .build()} +connected + +read zilla:reset.ext ${filesystem:resetEx() + .typeId(zilla:id("filesystem")) + .error("FILE_NOT_FOUND") + .build()} + +write aborted diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.delete.non.existent.file/server.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.delete.non.existent.file/server.rpt new file mode 100644 index 0000000000..7a0da09e1c --- /dev/null +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.delete.non.existent.file/server.rpt @@ -0,0 +1,33 @@ +# +# Copyright 2021-2024 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. +# + +accept "zilla://streams/filesystem0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${filesystem:matchBeginEx() + .typeId(zilla:id("filesystem")) + .capabilities("DELETE_PAYLOAD") + .path("index.html") + .build()} +connected + +write zilla:reset.ext ${filesystem:resetEx() + .typeId(zilla:id("filesystem")) + .error("FILE_NOT_FOUND") + .build()} + +read abort diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.write.file.failed/client.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.write.file.failed/client.rpt new file mode 100644 index 0000000000..35c3b0c8ca --- /dev/null +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.write.file.failed/client.rpt @@ -0,0 +1,33 @@ +# +# Copyright 2021-2024 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +connect "zilla://streams/filesystem0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${filesystem:beginEx() + .typeId(zilla:id("filesystem")) + .capabilities("WRITE_PAYLOAD") + .path("index.html") + .tag("c7183509522eb56e5cf927a3b2e8c15a") + .build()} +connected + +read zilla:reset.ext ${filesystem:resetEx() + .typeId(zilla:id("filesystem")) + .error("FILE_MODIFIED") + .build()} + +write aborted diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.write.file.failed/server.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.write.file.failed/server.rpt new file mode 100644 index 0000000000..ffa9df505d --- /dev/null +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.write.file.failed/server.rpt @@ -0,0 +1,34 @@ +# +# Copyright 2021-2024 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. +# + +accept "zilla://streams/filesystem0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${filesystem:matchBeginEx() + .typeId(zilla:id("filesystem")) + .capabilities("WRITE_PAYLOAD") + .path("index.html") + .tag("c7183509522eb56e5cf927a3b2e8c15a") + .build()} +connected + +write zilla:reset.ext ${filesystem:resetEx() + .typeId(zilla:id("filesystem")) + .error("FILE_MODIFIED") + .build()} + +read abort diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.write.file/client.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.write.file/client.rpt new file mode 100644 index 0000000000..36860eb001 --- /dev/null +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.write.file/client.rpt @@ -0,0 +1,43 @@ +# +# Copyright 2021-2024 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +connect "zilla://streams/filesystem0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${filesystem:beginEx() + .typeId(zilla:id("filesystem")) + .capabilities("WRITE_PAYLOAD") + .path("index.html") + .tag("c7183509522eb56e5cf927a3b2e8c15a") + .build()} +connected + +write "\n" + "Welcome\n" + "Hello, zilla\n" + "\n" +write flush + +write close + +read zilla:begin.ext ${filesystem:matchBeginEx() + .typeId(zilla:id("filesystem")) + .capabilities("WRITE_PAYLOAD") + .path("index.html") + .tag("c3ce2fda552e43985939a773852f45d1") + .build()} + +read closed diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.write.file/server.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.write.file/server.rpt new file mode 100644 index 0000000000..c6844cf4dc --- /dev/null +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/filesystem/client.write.file/server.rpt @@ -0,0 +1,44 @@ +# +# Copyright 2021-2024 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. +# + +accept "zilla://streams/filesystem0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${filesystem:matchBeginEx() + .typeId(zilla:id("filesystem")) + .capabilities("WRITE_PAYLOAD") + .path("index.html") + .tag("c7183509522eb56e5cf927a3b2e8c15a") + .build()} +connected + +read "\n" + "Welcome\n" + "Hello, zilla\n" + "\n" + +read closed + +write zilla:begin.ext ${filesystem:beginEx() + .typeId(zilla:id("filesystem")) + .capabilities("WRITE_PAYLOAD") + .path("index.html") + .tag("c3ce2fda552e43985939a773852f45d1") + .build()} +write flush + +write close diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.create.existing.file.failed/client.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.create.existing.file.failed/client.rpt new file mode 100644 index 0000000000..715d592d5d --- /dev/null +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.create.existing.file.failed/client.rpt @@ -0,0 +1,36 @@ +# +# Copyright 2021-2024 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +connect "zilla://streams/http0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "https") + .header(":authority", "example.com:9090") + .header(":path", "/index.html") + .build()} + +connected + +read zilla:reset.ext ${http:resetEx() + .typeId(zilla:id("http")) + .header(":status", "409") + .header("content-length", "0") + .build()} + +write aborted diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.create.existing.file.failed/server.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.create.existing.file.failed/server.rpt new file mode 100644 index 0000000000..3d6fc6c537 --- /dev/null +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.create.existing.file.failed/server.rpt @@ -0,0 +1,37 @@ +# +# Copyright 2021-2024 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. +# + +accept "zilla://streams/http0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "https") + .header(":authority", "example.com:9090") + .header(":path", "/index.html") + .build()} + +connected + +write zilla:reset.ext ${http:resetEx() + .typeId(zilla:id("http")) + .header(":status", "409") + .header("content-length", "0") + .build()} + +read abort diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.create.file/client.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.create.file/client.rpt new file mode 100644 index 0000000000..4fe70d3200 --- /dev/null +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.create.file/client.rpt @@ -0,0 +1,45 @@ +# +# Copyright 2021-2024 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +connect "zilla://streams/http0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "https") + .header(":authority", "example.com:9090") + .header(":path", "/index.html") + .build()} + +connected + +write "\n" + "Welcome\n" + "Hello, world\n" + "\n" + +write flush + +write close + +read zilla:begin.ext ${http:matchBeginEx() + .typeId(zilla:id("http")) + .header(":status", "204") + .header("etag", "c7183509522eb56e5cf927a3b2e8c15a") + .build()} + +read closed diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.create.file/server.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.create.file/server.rpt new file mode 100644 index 0000000000..015c0251d8 --- /dev/null +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.create.file/server.rpt @@ -0,0 +1,46 @@ +# +# Copyright 2021-2024 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. +# + +accept "zilla://streams/http0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "https") + .header(":authority", "example.com:9090") + .header(":path", "/index.html") + .build()} + +connected + +read "\n" + "Welcome\n" + "Hello, world\n" + "\n" + +read closed + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "204") + .header("etag", "c7183509522eb56e5cf927a3b2e8c15a") + .build()} + +write flush + +write close diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.write.file.rejected/client.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.delete.file/client.rpt similarity index 78% rename from specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.write.file.rejected/client.rpt rename to specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.delete.file/client.rpt index 7812934074..23ed0c6862 100644 --- a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.write.file.rejected/client.rpt +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.delete.file/client.rpt @@ -19,10 +19,19 @@ connect "zilla://streams/http0" write zilla:begin.ext ${http:beginEx() .typeId(zilla:id("http")) - .header(":method", "PUT") + .header(":method", "DELETE") .header(":scheme", "https") .header(":authority", "example.com:9090") .header(":path", "/index.html") .build()} -connect aborted +connected + +write close + +read zilla:begin.ext ${http:matchBeginEx() + .typeId(zilla:id("http")) + .header(":status", "204") + .build()} + +read closed diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.delete.file/server.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.delete.file/server.rpt new file mode 100644 index 0000000000..1b92656275 --- /dev/null +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.delete.file/server.rpt @@ -0,0 +1,40 @@ +# +# Copyright 2021-2024 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. +# + +accept "zilla://streams/http0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "DELETE") + .header(":scheme", "https") + .header(":authority", "example.com:9090") + .header(":path", "/index.html") + .build()} + +connected + +read closed + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "204") + .build()} + +write flush + +write close diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.delete.non.existent.file/client.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.delete.non.existent.file/client.rpt new file mode 100644 index 0000000000..af83c5ffdd --- /dev/null +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.delete.non.existent.file/client.rpt @@ -0,0 +1,36 @@ +# +# Copyright 2021-2024 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +connect "zilla://streams/http0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "DELETE") + .header(":scheme", "https") + .header(":authority", "example.com:9090") + .header(":path", "/index.html") + .build()} + +connected + +read zilla:reset.ext ${http:resetEx() + .typeId(zilla:id("http")) + .header(":status", "404") + .header("content-length", "0") + .build()} + +write aborted diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.delete.non.existent.file/server.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.delete.non.existent.file/server.rpt new file mode 100644 index 0000000000..07e32869aa --- /dev/null +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.delete.non.existent.file/server.rpt @@ -0,0 +1,37 @@ +# +# Copyright 2021-2024 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. +# + +accept "zilla://streams/http0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "DELETE") + .header(":scheme", "https") + .header(":authority", "example.com:9090") + .header(":path", "/index.html") + .build()} + +connected + +write zilla:reset.ext ${http:resetEx() + .typeId(zilla:id("http")) + .header(":status", "404") + .header("content-length", "0") + .build()} + +read abort diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.write.file.failed/client.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.write.file.failed/client.rpt new file mode 100644 index 0000000000..60e407af96 --- /dev/null +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.write.file.failed/client.rpt @@ -0,0 +1,37 @@ +# +# Copyright 2021-2024 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +connect "zilla://streams/http0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "PUT") + .header(":scheme", "https") + .header(":authority", "example.com:9090") + .header(":path", "/index.html") + .header("if-match", "c7183509522eb56e5cf927a3b2e8c15a") + .build()} + +connected + +read zilla:reset.ext ${http:resetEx() + .typeId(zilla:id("http")) + .header(":status", "412") + .header("content-length", "0") + .build()} + +write aborted diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.write.file.failed/server.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.write.file.failed/server.rpt new file mode 100644 index 0000000000..7d924c7f8d --- /dev/null +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.write.file.failed/server.rpt @@ -0,0 +1,38 @@ +# +# Copyright 2021-2024 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. +# + +accept "zilla://streams/http0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "PUT") + .header(":scheme", "https") + .header(":authority", "example.com:9090") + .header(":path", "/index.html") + .header("if-match", "c7183509522eb56e5cf927a3b2e8c15a") + .build()} + +connected + +write zilla:reset.ext ${http:resetEx() + .typeId(zilla:id("http")) + .header(":status", "412") + .header("content-length", "0") + .build()} + +read abort diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.write.file/client.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.write.file/client.rpt new file mode 100644 index 0000000000..3363f7374d --- /dev/null +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.write.file/client.rpt @@ -0,0 +1,46 @@ +# +# Copyright 2021-2024 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +connect "zilla://streams/http0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "PUT") + .header(":scheme", "https") + .header(":authority", "example.com:9090") + .header(":path", "/index.html") + .header("if-match", "c7183509522eb56e5cf927a3b2e8c15a") + .build()} + +connected + +write "\n" + "Welcome\n" + "Hello, zilla\n" + "\n" + +write flush + +write close + +read zilla:begin.ext ${http:matchBeginEx() + .typeId(zilla:id("http")) + .header(":status", "204") + .header("etag", "c3ce2fda552e43985939a773852f45d1") + .build()} + +read closed diff --git a/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.write.file/server.rpt b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.write.file/server.rpt new file mode 100644 index 0000000000..bdb232346a --- /dev/null +++ b/specs/binding-http-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/filesystem/streams/http/client.write.file/server.rpt @@ -0,0 +1,47 @@ +# +# Copyright 2021-2024 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. +# + +accept "zilla://streams/http0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "PUT") + .header(":scheme", "https") + .header(":authority", "example.com:9090") + .header(":path", "/index.html") + .header("if-match", "c7183509522eb56e5cf927a3b2e8c15a") + .build()} + +connected + +read "\n" + "Welcome\n" + "Hello, zilla\n" + "\n" + +read closed + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "204") + .header("etag", "c3ce2fda552e43985939a773852f45d1") + .build()} + +write flush + +write close diff --git a/specs/binding-http-filesystem.spec/src/test/java/io/aklivity/zilla/specs/binding/http/filesystem/streams/FileSystemIT.java b/specs/binding-http-filesystem.spec/src/test/java/io/aklivity/zilla/specs/binding/http/filesystem/streams/FileSystemIT.java index 53fd4faa3d..fc5b9b4b3f 100644 --- a/specs/binding-http-filesystem.spec/src/test/java/io/aklivity/zilla/specs/binding/http/filesystem/streams/FileSystemIT.java +++ b/specs/binding-http-filesystem.spec/src/test/java/io/aklivity/zilla/specs/binding/http/filesystem/streams/FileSystemIT.java @@ -116,4 +116,58 @@ public void shouldReceiveClientSentAbort() throws Exception { k3po.finish(); } + + @Test + @Specification({ + "${filesystem}/client.create.file/client", + "${filesystem}/client.create.file/server"}) + public void shouldReceiveClientCreateFile() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${filesystem}/client.create.existing.file.failed/client", + "${filesystem}/client.create.existing.file.failed/server"}) + public void shouldRejectClientCreateExistingFileFailed() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${filesystem}/client.write.file/client", + "${filesystem}/client.write.file/server"}) + public void shouldReceiveClientWriteFile() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${filesystem}/client.write.file.failed/client", + "${filesystem}/client.write.file.failed/server"}) + public void shouldRejectClientWriteFileFailed() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${filesystem}/client.delete.file/client", + "${filesystem}/client.delete.file/server"}) + public void shouldReceiveClientDeleteFile() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${filesystem}/client.delete.non.existent.file/client", + "${filesystem}/client.delete.non.existent.file/server"}) + public void shouldRejectClientDeleteNonExistentFile() throws Exception + { + k3po.finish(); + } } diff --git a/specs/binding-http-filesystem.spec/src/test/java/io/aklivity/zilla/specs/binding/http/filesystem/streams/HttpIT.java b/specs/binding-http-filesystem.spec/src/test/java/io/aklivity/zilla/specs/binding/http/filesystem/streams/HttpIT.java index 321d2d9fa7..173d9892c1 100644 --- a/specs/binding-http-filesystem.spec/src/test/java/io/aklivity/zilla/specs/binding/http/filesystem/streams/HttpIT.java +++ b/specs/binding-http-filesystem.spec/src/test/java/io/aklivity/zilla/specs/binding/http/filesystem/streams/HttpIT.java @@ -91,15 +91,6 @@ public void shouldRejectClient() throws Exception k3po.finish(); } - @Test - @Specification({ - "${http}/client.write.file.rejected/client", - "${http}/client.write.file.rejected/server"}) - public void shouldRejectClientWriteFile() throws Exception - { - k3po.finish(); - } - @Test @Specification({ "${http}/client.sent.message/client", @@ -153,4 +144,58 @@ public void shouldReceiveServerSentFlush() throws Exception { k3po.finish(); } + + @Test + @Specification({ + "${http}/client.create.file/client", + "${http}/client.create.file/server"}) + public void shouldReceiveClientCreateFile() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${http}/client.create.existing.file.failed/client", + "${http}/client.create.existing.file.failed/server"}) + public void shouldRejectClientCreateExistingFileFailed() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${http}/client.write.file/client", + "${http}/client.write.file/server"}) + public void shouldReceiveClientWriteFile() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${http}/client.write.file.failed/client", + "${http}/client.write.file.failed/server"}) + public void shouldRejectClientWriteFileFailed() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${http}/client.delete.file/client", + "${http}/client.delete.file/server"}) + public void shouldReceiveClientDeleteFile() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${http}/client.delete.non.existent.file/client", + "${http}/client.delete.non.existent.file/server"}) + public void shouldRejectClientDeleteNonExistentFile() throws Exception + { + k3po.finish(); + } }