Init Commit

This commit is contained in:
hstyi
2025-01-02 10:51:54 +08:00
commit 470b95cc42
418 changed files with 29687 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package app.termora;
public interface Disposable {
default void dispose() {
}
interface Parent extends Disposable {
void beforeTreeDispose();
}
}

View File

@@ -0,0 +1,192 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package app.termora;
import org.jetbrains.annotations.*;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.function.Predicate;
/**
* <p>Manages a parent-child relation of chained objects requiring cleanup.</p>
*
* <p>A root node can be created via {@link #newDisposable()}, to which children are attached via subsequent calls to {@link #register(Disposable, Disposable)}.
* Invoking {@link #dispose(Disposable)} will process all its registered children's {@link Disposable#dispose()} method.</p>
* <p>
* See <a href="https://www.jetbrains.org/intellij/sdk/docs/basics/disposers.html">Disposer and Disposable</a> in SDK Docs.
*
* @see Disposable
*/
public final class Disposer {
private static final ObjectTree ourTree = new ObjectTree();
public static boolean isDebugDisposerOn() {
return "on".equals(System.getProperty("idea.disposer.debug"));
}
private static boolean ourDebugMode;
private Disposer() {
}
@NotNull
@Contract(pure = true, value = "->new")
public static Disposable newDisposable() {
// must not be lambda because we care about identity in ObjectTree.myObject2NodeMap
return newDisposable("");
}
@NotNull
@Contract(pure = true, value = "_->new")
public static Disposable newDisposable(@NotNull @NonNls String debugName) {
// must not be lambda because we care about identity in ObjectTree.myObject2NodeMap
return new Disposable() {
@Override
public void dispose() {
}
@Override
public String toString() {
return debugName;
}
};
}
@Contract(pure = true, value = "_,_->new")
public static @NotNull Disposable newDisposable(@NotNull Disposable parentDisposable, @NotNull String debugName) {
Disposable result = newDisposable(debugName);
register(parentDisposable, result);
return result;
}
private static final Map<String, Disposable> ourKeyDisposables = Collections.synchronizedMap(new WeakHashMap<>());
public static void register(@NotNull Disposable parent, @NotNull Disposable child) {
RuntimeException e = ourTree.register(parent, child);
if (e != null) throw e;
}
/**
* Registers child disposable under parent unless the parent has already been disposed
*
* @return whether the registration succeeded
*/
public static boolean tryRegister(@NotNull Disposable parent, @NotNull Disposable child) {
return ourTree.register(parent, child) == null;
}
public static void register(@NotNull Disposable parent, @NotNull Disposable child, @NonNls @NotNull final String key) {
register(parent, child);
Disposable v = get(key);
if (v != null) throw new IllegalArgumentException("Key " + key + " already registered: " + v);
ourKeyDisposables.put(key, child);
register(child, new KeyDisposable(key));
}
private static final class KeyDisposable implements Disposable {
@NotNull
private final String myKey;
KeyDisposable(@NotNull String key) {
myKey = key;
}
@Override
public void dispose() {
ourKeyDisposables.remove(myKey);
}
@Override
public String toString() {
return "KeyDisposable (" + myKey + ")";
}
}
/**
* @return true if {@code disposable} is disposed or being disposed (i.e. its {@link Disposable#dispose()} method is executing).
*/
public static boolean isDisposed(@NotNull Disposable disposable) {
return ourTree.getDisposalInfo(disposable) != null;
}
/**
* @deprecated use {@link #isDisposed(Disposable)} instead
*/
@Deprecated
public static boolean isDisposing(@NotNull Disposable disposable) {
return isDisposed(disposable);
}
public static Disposable get(@NotNull String key) {
return ourKeyDisposables.get(key);
}
public static void dispose(@NotNull Disposable disposable) {
dispose(disposable, true);
}
/**
* {@code predicate} is used only for direct children.
*/
@ApiStatus.Internal
public static void disposeChildren(@NotNull Disposable disposable, @Nullable Predicate<? super Disposable> predicate) {
ourTree.executeAllChildren(disposable, predicate);
}
public static void dispose(@NotNull Disposable disposable, boolean processUnregistered) {
ourTree.executeAll(disposable, processUnregistered);
}
@NotNull
public static ObjectTree getTree() {
return ourTree;
}
public static void assertIsEmpty() {
assertIsEmpty(false);
}
public static void assertIsEmpty(boolean throwError) {
if (ourDebugMode) {
ourTree.assertIsEmpty(throwError);
}
}
/**
* @return old value
*/
public static boolean setDebugMode(boolean debugMode) {
if (debugMode) {
debugMode = !"off".equals(System.getProperty("idea.disposer.debug"));
}
boolean oldValue = ourDebugMode;
ourDebugMode = debugMode;
return oldValue;
}
public static boolean isDebugMode() {
return ourDebugMode;
}
/**
* @return object registered on {@code parentDisposable} which is equal to object, or {@code null} if not found
*/
@Nullable
public static <T extends Disposable> T findRegisteredObject(@NotNull Disposable parentDisposable, @NotNull T object) {
return ourTree.findRegisteredObject(parentDisposable, object);
}
public static Throwable getDisposalTrace(@NotNull Disposable disposable) {
if (getTree().getDisposalInfo(disposable) instanceof Throwable) {
return (Throwable) disposable;
}
return null;
}
@ApiStatus.Internal
public static void clearDisposalTraces() {
ourTree.clearDisposedObjectTraces();
}
}

View File

@@ -0,0 +1,128 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package app.termora;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
final class ObjectNode {
private final ObjectTree myTree;
ObjectNode myParent; // guarded by myTree.treeLock
private final Disposable myObject;
private List<ObjectNode> myChildren; // guarded by myTree.treeLock
private Throwable myTrace; // guarded by myTree.treeLock
ObjectNode(@NotNull ObjectTree tree,
@Nullable ObjectNode parentNode,
@NotNull Disposable object) {
myTree = tree;
myParent = parentNode;
myObject = object;
}
void addChild(@NotNull ObjectNode child) {
List<ObjectNode> children = myChildren;
if (children == null) {
myChildren = new ArrayList<>();
myChildren.add(child);
} else {
children.add(child);
}
child.myParent = this;
}
void removeChild(@NotNull ObjectNode child) {
List<ObjectNode> children = myChildren;
if (children != null) {
// optimisation: iterate backwards
for (int i = children.size() - 1; i >= 0; i--) {
ObjectNode node = children.get(i);
if (node.equals(child)) {
children.remove(i);
break;
}
}
}
child.myParent = null;
}
ObjectNode getParent() {
return myParent;
}
void getAndRemoveRecursively(@NotNull List<? super Disposable> result) {
getAndRemoveChildrenRecursively(result, null);
myTree.removeObjectFromTree(this);
// already disposed. may happen when someone does `register(obj, ()->Disposer.dispose(t));` abomination
if (myTree.rememberDisposedTrace(myObject) == null) {
result.add(myObject);
}
myChildren = null;
myParent = null;
}
/**
* {@code predicate} is used only for direct children.
*/
void getAndRemoveChildrenRecursively(@NotNull List<? super Disposable> result, @Nullable Predicate<? super Disposable> predicate) {
if (myChildren != null) {
for (int i = myChildren.size() - 1; i >= 0; i--) {
ObjectNode childNode = myChildren.get(i);
if (predicate == null || predicate.test(childNode.getObject())) {
childNode.getAndRemoveRecursively(result);
}
}
}
}
@NotNull
Disposable getObject() {
return myObject;
}
@Override
@NonNls
public String toString() {
return "Node: " + myObject;
}
Throwable getTrace() {
return myTrace;
}
void clearTrace() {
myTrace = null;
}
@TestOnly
void assertNoReferencesKept(@NotNull Disposable aDisposable) {
assert getObject() != aDisposable;
if (myChildren != null) {
for (ObjectNode node : myChildren) {
node.assertNoReferencesKept(aDisposable);
}
}
}
<D extends Disposable> D findChildEqualTo(@NotNull D object) {
List<ObjectNode> children = myChildren;
if (children != null) {
for (ObjectNode node : children) {
Disposable nodeObject = node.getObject();
if (nodeObject.equals(object)) {
//noinspection unchecked
return (D) nodeObject;
}
}
}
return null;
}
}

View File

@@ -0,0 +1,246 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package app.termora;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.util.*;
import java.util.function.Predicate;
import java.util.function.Supplier;
final class ObjectTree {
private static final ThreadLocal<Throwable> ourTopmostDisposeTrace = new ThreadLocal<>();
private final Set<Disposable> myRootObjects = new HashSet<>();
// guarded by treeLock
private final Map<Disposable, ObjectNode> myObject2NodeMap = new HashMap<>();
// Disposable -> trace or boolean marker (if trace unavailable)
private final Map<Disposable, Object> myDisposedObjects = new WeakHashMap<>(); // guarded by treeLock
private final Object treeLock = new Object();
private ObjectNode getNode(@NotNull Disposable object) {
return myObject2NodeMap.get(object);
}
private void putNode(@NotNull Disposable object, @Nullable("null means remove") ObjectNode node) {
if (node == null) {
myObject2NodeMap.remove(object);
} else {
myObject2NodeMap.put(object, node);
}
}
@Nullable
final RuntimeException register(@NotNull Disposable parent, @NotNull Disposable child) {
if (parent == child) return new IllegalArgumentException("Cannot register to itself: " + parent);
synchronized (treeLock) {
Object wasDisposed = getDisposalInfo(parent);
if (wasDisposed != null) {
return new IllegalStateException("Sorry but parent: " + parent + " has already been disposed " +
"(see the cause for stacktrace) so the child: " + child + " will never be disposed",
wasDisposed instanceof Throwable ? (Throwable) wasDisposed : null);
}
myDisposedObjects.remove(child); // if we dispose thing and then register it back it means it's not disposed anymore
ObjectNode parentNode = getNode(parent);
if (parentNode == null) parentNode = createNodeFor(parent, null);
ObjectNode childNode = getNode(child);
if (childNode == null) {
childNode = createNodeFor(child, parentNode);
} else {
ObjectNode oldParent = childNode.getParent();
if (oldParent != null) {
oldParent.removeChild(childNode);
}
}
myRootObjects.remove(child);
RuntimeException e = checkWasNotAddedAlready(parentNode, childNode);
if (e != null) return e;
parentNode.addChild(childNode);
}
return null;
}
Object getDisposalInfo(@NotNull Disposable object) {
synchronized (treeLock) {
return myDisposedObjects.get(object);
}
}
private static RuntimeException checkWasNotAddedAlready(@NotNull ObjectNode childNode, @NotNull ObjectNode parentNode) {
for (ObjectNode node = childNode; node != null; node = node.getParent()) {
if (node == parentNode) {
return new IllegalStateException("'" + childNode.getObject() + "' was already added as a child of '" + parentNode.getObject() + "'");
}
}
return null;
}
@NotNull
private ObjectNode createNodeFor(@NotNull Disposable object, @Nullable ObjectNode parentNode) {
final ObjectNode newNode = new ObjectNode(this, parentNode, object);
if (parentNode == null) {
myRootObjects.add(object);
}
putNode(object, newNode);
return newNode;
}
private void runWithTrace(@NotNull Supplier<? extends @NotNull List<Disposable>> removeFromTreeAction) {
boolean needTrace = Disposer.isDebugMode() && ourTopmostDisposeTrace.get() == null;
if (needTrace) {
ourTopmostDisposeTrace.set(new Throwable());
}
// first, atomically remove disposables from the tree to avoid "register during dispose" race conditions
List<Disposable> disposables;
synchronized (treeLock) {
disposables = removeFromTreeAction.get();
}
// second, call "beforeTreeDispose" in pre-order (some clients are hardcoded to see parents-then-children order in "beforeTreeDispose")
List<Throwable> exceptions = null;
for (int i = disposables.size() - 1; i >= 0; i--) {
Disposable disposable = disposables.get(i);
if (disposable instanceof Disposable.Parent) {
try {
((Disposable.Parent) disposable).beforeTreeDispose();
} catch (Throwable t) {
if (exceptions == null) exceptions = new ArrayList<>();
exceptions.add(t);
}
}
}
// third, dispose in post-order (bottom-up)
for (Disposable disposable : disposables) {
try {
//noinspection SSBasedInspection
disposable.dispose();
} catch (Throwable e) {
if (exceptions == null) exceptions = new ArrayList<>();
exceptions.add(e);
}
}
if (needTrace) {
ourTopmostDisposeTrace.remove();
}
if (exceptions != null) {
handleExceptions(exceptions);
}
}
void executeAllChildren(@NotNull Disposable object, @Nullable Predicate<? super Disposable> predicate) {
runWithTrace(() -> {
ObjectNode node = getNode(object);
if (node == null) {
return Collections.emptyList();
}
List<Disposable> disposables = new ArrayList<>();
node.getAndRemoveChildrenRecursively(disposables, predicate);
return disposables;
});
}
void executeAll(@NotNull Disposable object, boolean processUnregistered) {
runWithTrace(() -> {
ObjectNode node = getNode(object);
if (node == null && !processUnregistered) {
return Collections.emptyList();
}
List<Disposable> disposables = new ArrayList<>();
if (node == null) {
if (rememberDisposedTrace(object) == null) {
disposables.add(object);
}
} else {
node.getAndRemoveRecursively(disposables);
}
return disposables;
});
}
private static void handleExceptions(@NotNull List<? extends Throwable> exceptions) {
}
@TestOnly
void assertNoReferenceKeptInTree(@NotNull Disposable disposable) {
synchronized (treeLock) {
for (Map.Entry<Disposable, ObjectNode> entry : myObject2NodeMap.entrySet()) {
Disposable key = entry.getKey();
assert key != disposable;
ObjectNode node = entry.getValue();
node.assertNoReferencesKept(disposable);
}
}
}
void assertIsEmpty(boolean throwError) {
synchronized (treeLock) {
for (Disposable object : myRootObjects) {
if (object == null) continue;
ObjectNode objectNode = getNode(object);
if (objectNode == null) continue;
while (objectNode.getParent() != null) {
objectNode = objectNode.getParent();
}
final Throwable trace = objectNode.getTrace();
RuntimeException exception = new RuntimeException("Memory leak detected: '" + object + "' of " + object.getClass()
+ "\nSee the cause for the corresponding Disposer.register() stacktrace:\n",
trace);
if (throwError) {
throw exception;
}
}
}
}
// return old value
Object rememberDisposedTrace(@NotNull Disposable object) {
synchronized (treeLock) {
Throwable trace = ourTopmostDisposeTrace.get();
return myDisposedObjects.put(object, trace != null ? trace : Boolean.TRUE);
}
}
void clearDisposedObjectTraces() {
synchronized (treeLock) {
myDisposedObjects.clear();
for (ObjectNode value : myObject2NodeMap.values()) {
value.clearTrace();
}
}
}
@Nullable
<D extends Disposable> D findRegisteredObject(@NotNull Disposable parentDisposable, @NotNull D object) {
synchronized (treeLock) {
ObjectNode parentNode = getNode(parentDisposable);
if (parentNode == null) return null;
return parentNode.findChildEqualTo(object);
}
}
void removeObjectFromTree(@NotNull ObjectNode node) {
synchronized (treeLock) {
Disposable myObject = node.getObject();
putNode(myObject, null);
ObjectNode parent = node.getParent();
if (parent == null) {
myRootObjects.remove(myObject);
} else {
parent.removeChild(node);
}
node.myParent = null;
}
}
}

View File

@@ -0,0 +1,34 @@
package zmodem
import org.apache.commons.net.io.CopyStreamEvent
/**
* 如果一共两个文件,并且传输第一个文件时:
*
* remaining = 1
* index = 1
*/
class FileCopyStreamEvent(
source: Any,
// 本次传输的文件名
val filename: String,
// 剩余未传输的文件数量
val remaining: Int,
// 第几个文件
val index: Int,
// 总字节数
totalBytesTransferred: Long,
// 已经传输完成的字节数
bytesTransferred: Int,
// 本次传输的字节数
streamSize: Long,
/**
* 这个文件被跳过了
*/
val skip: Boolean = false,
) :
CopyStreamEvent(
source, totalBytesTransferred,
bytesTransferred,
streamSize
)

View File

@@ -0,0 +1,24 @@
package zmodem;
import zmodem.xfer.zm.util.Modem;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Path;
public class XModem {
private Modem modem;
public XModem(InputStream inputStream, OutputStream outputStream) {
this.modem = new Modem(inputStream, outputStream);
}
public void send(Path file, boolean useBlock1K) throws IOException, InterruptedException {
modem.send(file, useBlock1K);
}
public void receive(Path file) throws IOException {
modem.receive(file, false);
}
}

View File

@@ -0,0 +1,207 @@
package zmodem;
import zmodem.xfer.util.CRC16;
import zmodem.xfer.util.CRC8;
import zmodem.xfer.util.XCRC;
import zmodem.xfer.zm.util.Modem;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
/**
* YModem.<br/>
* Block 0 contain minimal file information (only filename)<br/>
* <p>
* Created by Anton Sirotinkin (aesirot@mail.ru), Moscow 2014<br/>
* I hope you will find this program useful.<br/>
* You are free to use/modify the code for any purpose, but please leave a reference to me.<br/>
* <br/>
*/
public class YModem {
private Modem modem;
/**
* Constructor
*
* @param inputStream stream for reading received data from other side
* @param outputStream stream for writing data to other side
*/
public YModem(InputStream inputStream, OutputStream outputStream) {
this.modem = new Modem(inputStream, outputStream);
}
/**
* Send a file.<br/>
* <p>
* This method support correct thread interruption, when thread is interrupted "cancel of transmission" will be send.
* So you can move long transmission to other thread and interrupt it according to your algorithm.
*
* @param file
* @throws IOException
*/
public void send(Path file) throws IOException {
//check filename
// if (!file.getFileName().toString().matches("\\w{1,8}\\.\\w{1,3}")) {
// throw new IOException("Filename must be in DOS style (no spaces, max 8.3)");
// }
//open file
try (DataInputStream dataStream = new DataInputStream(Files.newInputStream(file))) {
boolean useCRC16 = modem.waitReceiverRequest();
XCRC crc;
if (useCRC16)
crc = new CRC16();
else
crc = new CRC8();
//send block 0
BasicFileAttributes readAttributes = Files.readAttributes(file, BasicFileAttributes.class);
String fileNameString = file.getFileName().toString() + (char) 0 + ((Long) Files.size(file)).toString() + " " + Long.toOctalString(readAttributes.lastModifiedTime().toMillis() / 1000);
byte[] fileNameBytes = Arrays.copyOf(fileNameString.getBytes(), 128);
modem.sendBlock(0, Arrays.copyOf(fileNameBytes, 128), 128, crc);
modem.waitReceiverRequest();
//send data
byte[] block = new byte[1024];
modem.sendDataBlocks(dataStream, 1, crc, block);
modem.sendEOT();
}
}
/**
* Send files in batch mode.<br/>
* <p>
* This method support correct thread interruption, when thread is interrupted "cancel of transmission" will be send.
* So you can move long transmission to other thread and interrupt it according to your algorithm.
*
* @param files
* @throws IOException
*/
public void batchSend(Path... files) throws IOException {
for (Path file : files) {
send(file);
}
sendBatchStop();
}
private void sendBatchStop() throws IOException {
boolean useCRC16 = modem.waitReceiverRequest();
XCRC crc;
if (useCRC16)
crc = new CRC16();
else
crc = new CRC8();
//send block 0
byte[] bytes = new byte[128];
modem.sendBlock(0, bytes, bytes.length, crc);
}
/**
* Receive single file <br/>
* <p>
* This method support correct thread interruption, when thread is interrupted "cancel of transmission" will be send.
* So you can move long transmission to other thread and interrupt it according to your algorithm.
*
* @param directory directory where file will be saved
* @return path to created file
* @throws IOException
*/
public Path receiveSingleFileInDirectory(Path directory) throws IOException {
return receive(directory, true);
}
/**
* Receive files in batch mode <br/>
* <p>
* This method support correct thread interruption, when thread is interrupted "cancel of transmission" will be send.
* So you can move long transmission to other thread and interrupt it according to your algorithm.
*
* @param directory directory where files will be saved
* @throws IOException
*/
public void receiveFilesInDirectory(Path directory) throws IOException {
while (receive(directory, true) != null) {
}
}
/**
* Receive path <br/>
* <p>
* This method support correct thread interruption, when thread is interrupted "cancel of transmission" will be send.
* So you can move long transmission to other thread and interrupt it according to your algorithm.
*
* @param path path to file where data will be saved
* @return path to file
* @throws IOException
*/
public Path receive(Path path) throws IOException {
return receive(path, false);
}
private Path receive(Path path, boolean inDirectory) throws IOException {
DataOutputStream dataOutput = null;
Path filePath;
try {
XCRC crc = new CRC16();
int errorCount = 0;
// process block 0
byte[] block;
int character;
while (true) {
character = modem.requestTransmissionStart(true);
try {
// read file name from zero block
block = modem.readBlock(0, (character == Modem.SOH), crc);
if (inDirectory) {
StringBuilder sb = new StringBuilder();
if (block[0] == 0) {
//this is stop block of batch file transfer
modem.sendByte(Modem.ACK);
return null;
}
for (int i = 0; i < block.length; i++) {
if (block[i] == 0) {
break;
}
sb.append((char) block[i]);
}
filePath = path.resolve(sb.toString());
} else {
filePath = path;
}
dataOutput = new DataOutputStream(Files.newOutputStream(filePath));
modem.sendByte(Modem.ACK);
break;
} catch (Modem.InvalidBlockException e) {
errorCount++;
if (errorCount == Modem.MAXERRORS) {
modem.interruptTransmission();
throw new IOException("Transmission aborted, error count exceeded max");
}
modem.sendByte(Modem.NAK);
} catch (Modem.RepeatedBlockException | Modem.SynchronizationLostException e) {
//fatal transmission error
modem.interruptTransmission();
throw new IOException("Fatal transmission error", e);
}
}
//receive data blocks
modem.receive(filePath, true);
} finally {
if (dataOutput != null) {
dataOutput.close();
}
}
return filePath;
}
}

View File

@@ -0,0 +1,46 @@
package zmodem;
import org.apache.commons.net.io.CopyStreamListener;
import zmodem.util.FileAdapter;
import zmodem.xfer.zm.util.ZModemReceive;
import zmodem.xfer.zm.util.ZModemSend;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
public class ZModem {
private final InputStream netIs;
private final OutputStream netOs;
private final AtomicBoolean isCancelled = new AtomicBoolean(false);
public ZModem(InputStream netin, OutputStream netout) {
netIs = netin;
netOs = netout;
}
public void receive(Supplier<FileAdapter> destDir, CopyStreamListener listener) throws IOException {
ZModemReceive sender = new ZModemReceive(destDir, netIs, netOs);
sender.addCopyStreamListener(listener);
sender.receive(isCancelled::get);
netOs.flush();
}
public void send(Supplier<List<FileAdapter>> filesSupplier, CopyStreamListener listener) throws IOException {
ZModemSend sender = new ZModemSend(filesSupplier, netIs, netOs);
sender.addCopyStreamListener(listener);
sender.send(isCancelled::get);
netOs.flush();
}
public void cancel() {
isCancelled.compareAndSet(false, true);
}
}

View File

@@ -0,0 +1,4 @@
/**
* https://github.com/scraymer/Zmodem-in-Java
*/
package zmodem;

View File

@@ -0,0 +1,65 @@
package zmodem.util;
import org.apache.commons.io.input.RandomAccessFileInputStream;
import java.io.*;
public class CustomFile implements FileAdapter {
File file = null;
public CustomFile(File file) {
super();
this.file = file;
}
@Override
public String getName() {
return file.getName();
}
@Override
public InputStream getInputStream() throws IOException {
return RandomAccessFileInputStream.builder()
.setCloseOnClose(true)
.setRandomAccessFile(new RandomAccessFile(file, "r"))
.setBufferSize(1024 * 8)
.get();
}
@Override
public OutputStream getOutputStream() throws IOException {
return getOutputStream(false);
}
@Override
public OutputStream getOutputStream(boolean append) throws IOException {
return new BufferedOutputStream(new FileOutputStream(file, append));
}
@Override
public FileAdapter getChild(String name) {
if (name.equals(file.getName())) {
return this;
} else if (file.isDirectory()) {
return new CustomFile(new File(file.getAbsolutePath(), name));
}
return null;
}
@Override
public long length() {
return file.length();
}
@Override
public boolean isDirectory() {
return file.isDirectory();
}
@Override
public boolean exists() {
return file.exists();
}
}

View File

@@ -0,0 +1,42 @@
package zmodem.util
import java.io.InputStream
import java.io.OutputStream
class EmptyFileAdapter private constructor() : FileAdapter {
companion object {
val instance by lazy { EmptyFileAdapter() }
}
override fun getName(): String {
TODO("Not yet implemented")
}
override fun getInputStream(): InputStream {
TODO("Not yet implemented")
}
override fun getOutputStream(): OutputStream {
TODO("Not yet implemented")
}
override fun getOutputStream(append: Boolean): OutputStream {
TODO("Not yet implemented")
}
override fun getChild(name: String?): FileAdapter {
TODO("Not yet implemented")
}
override fun length(): Long {
TODO("Not yet implemented")
}
override fun isDirectory(): Boolean {
return true
}
override fun exists(): Boolean {
return true
}
}

View File

@@ -0,0 +1,25 @@
package zmodem.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public interface FileAdapter {
public String getName();
public InputStream getInputStream() throws IOException;
public OutputStream getOutputStream() throws IOException;
public OutputStream getOutputStream(boolean append) throws IOException;
public FileAdapter getChild(String name);
public long length();
public boolean isDirectory();
public boolean exists();
public String toString();
}

View File

@@ -0,0 +1,7 @@
package zmodem.xfer.io;
import java.io.IOException;
public abstract class ObjectInputStream<T> {
public abstract T read() throws IOException;
}

View File

@@ -0,0 +1,7 @@
package zmodem.xfer.io;
import java.io.IOException;
public abstract class ObjectOutputStream<T> {
public abstract void write(T o) throws IOException;
}

View File

@@ -0,0 +1,27 @@
package zmodem.xfer.util;
public enum ASCII {
SOH((byte) 0x01),
STX((byte) 0x02),
EOT((byte) 0x04),
ENQ((byte) 0x05),
ACK((byte) 0x06),
BS((byte) 0x08),
LF((byte) 0x0a),
CR((byte) 0x0d),
XON((byte) 0x11),
XOFF((byte) 0x13),
NAK((byte) 0x15),
CAN((byte) 0x18);
private byte value;
private ASCII(byte b) {
value = b;
}
public byte value() {
return value;
}
}

View File

@@ -0,0 +1,91 @@
package zmodem.xfer.util;
/**
* copyOf is not in java 5 java.util.Array
*
* @author justin
*/
public class Arrays {
public enum Endianness {Little, Big;}
public static byte[] copyOf(byte[] original, int newLength) {
byte[] copy = new byte[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
public static boolean equals(byte[] a, byte[] a2) {
return java.util.Arrays.equals(a, a2);
}
public static long toInteger(byte[] array, int size, Endianness endian) {
long n = 0;
int offset = 0, increment = 1;
switch (endian) {
case Little:
increment = 1;
offset = 0;
break;
case Big:
increment = -1;
offset = size - 1;
break;
}
for (int i = 0; i < size; i++) {
n += (0xff & array[offset]) * (0x1 << i * 8);
offset += increment;
}
return n;
}
public static short toShort(byte[] array, Endianness endian) {
return (short) toInteger(array, 2, endian);
}
public static int toInt(byte[] array, Endianness endian) {
return (int) toInteger(array, 4, endian);
}
public static long toLong(byte[] array, Endianness endian) {
return toInteger(array, 8, endian);
}
public static byte[] fromInteger(long n, int size, Endianness endian) {
byte[] ret = new byte[size];
int offset = 0, increment = 1;
switch (endian) {
case Big:
increment = -1;
offset = size - 1;
break;
case Little:
increment = 1;
offset = 0;
break;
}
for (int i = 0; i < size; i++) {
ret[offset] = (byte) ((n >> (i * 8)) & 0xFF);
offset += increment;
}
return ret;
}
public static byte[] fromShort(short s, Endianness endian) {
return fromInteger(s, 2, endian);
}
public static byte[] fromInt(int i, Endianness endian) {
return fromInteger(i, 4, endian);
}
public static byte[] fromLong(long i, Endianness endian) {
return fromInteger(i, 8, endian);
}
}

View File

@@ -0,0 +1,38 @@
package zmodem.xfer.util;
public interface Buffer {
public byte get();
public Buffer get(byte[] dst, int offset, int len);
public Buffer get(byte[] dst);
public Buffer put(byte b);
public Buffer put(byte[] dst, int offset, int len);
public Buffer put(byte[] dst);
public byte get(int index);
public Buffer get(int index, byte[] dst, int offset, int len);
public Buffer get(int index, byte[] dst);
public Buffer put(int index, byte b);
public Buffer put(int index, byte[] dst, int offset, int len);
public Buffer put(int index, byte[] dst);
public void flip();
public int remaining();
public boolean hasRemaining();
public HexBuffer asHexBuffer();
public ByteBuffer asByteBuffer();
}

View File

@@ -0,0 +1,193 @@
package zmodem.xfer.util;
public class ByteBuffer implements Buffer {
private java.nio.ByteBuffer _wrapped;
public ByteBuffer(java.nio.ByteBuffer b) {
_wrapped = b;
}
public static ByteBuffer allocate(int capacity) {
return new ByteBuffer(java.nio.ByteBuffer.allocate(capacity));
}
public static ByteBuffer allocateDirect(int capacity) {
return new ByteBuffer(java.nio.ByteBuffer.allocateDirect(capacity));
}
public Buffer slice() {
return new ByteBuffer(_wrapped.slice());
}
public Buffer duplicate() {
return new ByteBuffer(_wrapped.duplicate());
}
public Buffer asReadOnlyBuffer() {
return new ByteBuffer(_wrapped.asReadOnlyBuffer());
}
public byte get() {
return _wrapped.get();
}
public Buffer get(byte[] dst, int offset, int len) {
for (; offset < len; offset++)
dst[offset] = get();
return this;
}
public Buffer get(byte[] dst) {
return get(dst, 0, dst.length);
}
public Buffer put(byte b) {
_wrapped.put(b);
return this;
}
public Buffer put(byte[] dst, int offset, int len) {
for (; offset < len; offset++)
put(dst[offset]);
return this;
}
public Buffer put(byte[] dst) {
return put(dst, 0, dst.length);
}
public byte get(int index) {
return _wrapped.get(index);
}
public Buffer get(int index, byte[] dst, int offset, int len) {
for (; offset < len; offset++)
dst[offset] = get(index++);
return this;
}
public Buffer get(int index, byte[] dst) {
return get(index, dst, 0, dst.length);
}
public Buffer put(int index, byte b) {
_wrapped.put(index, b);
return this;
}
public Buffer put(int index, byte[] dst, int offset, int len) {
for (; offset < len; offset++)
put(index++, dst[offset]);
return this;
}
public Buffer put(int index, byte[] dst) {
return put(index, dst, 0, dst.length);
}
public Buffer compact() {
_wrapped.compact();
return this;
}
public boolean isDirect() {
return _wrapped.isDirect();
}
public char getChar() {
return (char) get();
}
public Buffer putChar(char value) {
return put((byte) value);
}
public char getChar(int index) {
return (char) get(index);
}
public Buffer putChar(int index, char value) {
return put(index, (byte) value);
}
public HexBuffer asHexBuffer() {
return new HexBuffer(_wrapped);
}
public short getShort() {
return Arrays.toShort(new byte[]{get(), get()}, Arrays.Endianness.Little);
}
public Buffer putShort(short value) {
return put(Arrays.fromShort(value, Arrays.Endianness.Little));
}
public short getShort(int index) {
return Arrays.toShort(new byte[]{get(index), get(index + 1)}, Arrays.Endianness.Little);
}
public Buffer putShort(int index, short value) {
return put(index, Arrays.fromShort(value, Arrays.Endianness.Little));
}
public int getInt() {
return Arrays.toInt(new byte[]{get(), get(), get(), get()}, Arrays.Endianness.Little);
}
public Buffer putInt(int value) {
return put(Arrays.fromInt(value, Arrays.Endianness.Little));
}
public int getInt(int index) {
return Arrays.toInt(new byte[]{get(index), get(index + 1), get(index + 2), get(index + 3)}, Arrays.Endianness.Little);
}
public Buffer putInt(int index, int value) {
return put(index, Arrays.fromInt(value, Arrays.Endianness.Little));
}
public long getLong() {
return Arrays.toLong(new byte[]{get(), get(), get(), get(), get(), get(), get(), get()}, Arrays.Endianness.Little);
}
public Buffer putLong(long value) {
return put(Arrays.fromLong(value, Arrays.Endianness.Little));
}
public long getLong(int index) {
return Arrays.toLong(new byte[]{get(index), get(index + 1), get(index + 2), get(index + 3), get(index + 4), get(index + 5), get(index + 6), get(index + 7)}, Arrays.Endianness.Little);
}
public Buffer putLong(int index, long value) {
return put(index, Arrays.fromLong(value, Arrays.Endianness.Little));
}
public boolean isReadOnly() {
return _wrapped.isReadOnly();
}
public void flip() {
_wrapped.flip();
}
public int remaining() {
return _wrapped.remaining();
}
public boolean hasRemaining() {
return remaining() > 0;
}
public ByteBuffer asByteBuffer() {
return this;
}
}

View File

@@ -0,0 +1,232 @@
package zmodem.xfer.util;
public class CRC {
public static enum Type {
CRC16(2, 0),
CRC32(4, 0xffffffff);
private int numbytes;
private int initial;
private Type(int s, int i) {
numbytes = s;
initial = i;
}
public int size() {
return numbytes;
}
public int initial() {
return initial;
}
}
/**
* Copied from the original zmodem source
*/
private static final int crctab[] = {
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
};
/**
* Copied from the original zmodem source
*/
private static final int cr3tab[] = { /* CRC polynomial 0xedb88320 */
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};
private static int updcrc(int cp, int crc) {
//System.out.printf("updcrc: %08x / %08x\n",cp,crc);
return (crctab[(crc >> 8) & 0xff] ^ (crc << 8) ^ cp);
}
private static int updcrc32(int b, int c) {
return (cr3tab[(c ^ b) & 0xff] ^ ((c >> 8) & 0x00FFFFFF));
}
public static byte[] arrayCRC(Type t, byte[] bytes) {
switch (t) {
case CRC16:
return arrayCRC16(bytes);
case CRC32:
return arrayCRC32(bytes);
}
return new byte[0];
}
/**
* @param bytes
* @return
*/
public static byte[] arrayCRC16(byte[] bytes) {
byte[] bb = new byte[2];
int value = CRC16(bytes);
bb[1] = (byte) (value & 0xFF);
bb[0] = (byte) ((value >> 8) & 0xFF);
return bb;
}
public static int CRC16(byte[] bytes) {
int crc = 0;
for (byte b : bytes)
crc = updcrc(0xff & b, crc);
crc = updcrc(0, updcrc(0, crc));
return crc;
}
/**
* @param bytes
* @return
*/
public static byte[] arrayCRC32(byte[] bytes) {
byte[] bb = new byte[4];
long value = CRC32(bytes);
bb[3] = (byte) (value & 0xFF);
bb[2] = (byte) ((value >> 8) & 0xFF);
bb[1] = (byte) ((value >> 16) & 0xFF);
bb[0] = (byte) ((value >> 24) & 0xFF);
return bb;
}
public static int CRC32(byte[] bytes) {
int crc = 0xFFFFFFFF;
for (byte b : bytes)
crc = updcrc32(b, crc);
return ~crc;
}
private Type type;
private int crc;
public CRC(Type t) {
type = t;
crc = type.initial();
}
public void update(byte b) {
switch (type) {
case CRC16:
crc = updcrc(0xff & b, crc);
break;
case CRC32:
crc = updcrc32((0xff & b), crc);
break;
}
}
public void update(byte[] array) {
for (byte b : array)
update(b);
}
public void finalized() {
switch (type) {
case CRC16:
crc = 0xffff & updcrc(0, updcrc(0, crc));
break;
case CRC32:
crc = ~crc;
break;
}
//System.out.printf("crc: %08x\n",crc);
}
public int size() {
return type.size();
}
public byte[] getBytes() {
byte[] bb;
switch (type) {
case CRC16:
bb = new byte[2];
bb[1] = (byte) (crc & 0xFF);
bb[0] = (byte) ((crc >> 8) & 0xFF);
return bb;
case CRC32:
bb = new byte[4];
bb[3] = (byte) (crc & 0xFF);
bb[2] = (byte) ((crc >> 8) & 0xFF);
bb[1] = (byte) ((crc >> 16) & 0xFF);
bb[0] = (byte) ((crc >> 24) & 0xFF);
return bb;
}
return new byte[0];
}
public Type type() {
return type;
}
}

View File

@@ -0,0 +1,58 @@
package zmodem.xfer.util;
/**
* Uses table for irreducible polynomial: 1 + x^2 + x^15 + x^16
*/
public class CRC16 implements XCRC {
private static int[] table = {
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0,
};
@Override
public int getCRCLength() {
return 2;
}
@Override
public long calcCRC(byte[] block) {
int crc = 0x0000;
for (byte b : block) {
crc = ((crc << 8) ^ table[((crc >> 8) ^ (0xff & b))]) & 0xFFFF;
}
return crc;
}
}

View File

@@ -0,0 +1,21 @@
package zmodem.xfer.util;
/**
* Created by asirotinkin on 11.11.2014.
*/
public class CRC8 implements XCRC {
@Override
public int getCRCLength() {
return 1;
}
@Override
public long calcCRC(byte[] block) {
byte checkSumma = 0;
for (int i = 0; i < block.length; i++) {
checkSumma += block[i];
}
return checkSumma;
}
}

View File

@@ -0,0 +1,235 @@
package zmodem.xfer.util;
import zmodem.xfer.util.Arrays.Endianness;
public class HexBuffer implements Buffer {
private static final byte[] hx = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
public static byte[] binToHex(byte[] bin) {
byte[] hex = new byte[bin.length * 2];
for (int i = 0; i < bin.length; i++)
System.arraycopy(toHex(bin[i]), 0, hex, i * 2, 2);
return hex;
}
public static byte[] hexToBin(byte[] hex) {
byte[] bin = new byte[hex.length / 2];
for (int i = 0; i < bin.length; i++) {
byte[] bn = new byte[2];
System.arraycopy(hex, i * 2, bn, 0, 2);
bin[i] = toByte(bn);
}
return bin;
}
private static byte toByte(byte[] array) {
int d;
d = java.util.Arrays.binarySearch(hx, array[0]) * 16;
d += java.util.Arrays.binarySearch(hx, array[1]);
return (byte) d;
}
private static byte[] toHex(byte b) {
byte[] array = new byte[2];
array[0] = hx[((b >> 4) & 0xF)];
array[1] = hx[(b & 0xF)];
return array;
}
private java.nio.ByteBuffer _wrapped;
protected HexBuffer(java.nio.ByteBuffer b) {
_wrapped = b;
}
public static HexBuffer allocate(int capacity) {
return new HexBuffer(java.nio.ByteBuffer.allocate(capacity * 2));
}
public static HexBuffer allocateDirect(int capacity) {
return new HexBuffer(java.nio.ByteBuffer.allocateDirect(capacity * 2));
}
public Buffer slice() {
return new HexBuffer(_wrapped.slice());
}
public Buffer duplicate() {
return new HexBuffer(_wrapped.duplicate());
}
public Buffer asReadOnlyBuffer() {
return new HexBuffer(_wrapped.asReadOnlyBuffer());
}
public byte get() {
return toByte(new byte[]{_wrapped.get(), _wrapped.get()});
}
public Buffer get(byte[] dst, int offset, int len) {
for (; offset < len; offset++)
dst[offset] = get();
return this;
}
public Buffer get(byte[] dst) {
return get(dst, 0, dst.length);
}
public Buffer put(byte b) {
_wrapped.put(toHex(b));
return this;
}
public Buffer put(byte[] dst, int offset, int len) {
for (; offset < len; offset++)
put(dst[offset]);
return this;
}
public Buffer put(byte[] dst) {
return put(dst, 0, dst.length);
}
public byte get(int index) {
return toByte(new byte[]{_wrapped.get(index * 2), _wrapped.get(index * 2 + 1)});
}
public Buffer get(int index, byte[] dst, int offset, int len) {
for (; offset < len; offset++)
dst[offset] = get(index++);
return this;
}
public Buffer get(int index, byte[] dst) {
return get(index, dst, 0, dst.length);
}
public Buffer put(int index, byte b) {
byte[] array = toHex(b);
_wrapped.put(index * 2, array[0]);
_wrapped.put(index * 2 + 1, array[1]);
return this;
}
public Buffer put(int index, byte[] dst, int offset, int len) {
for (; offset < len; offset++)
put(index++, dst[offset]);
return this;
}
public Buffer put(int index, byte[] dst) {
return put(index, dst, 0, dst.length);
}
public Buffer compact() {
_wrapped.compact();
return this;
}
public boolean isDirect() {
return _wrapped.isDirect();
}
public char getChar() {
return (char) get();
}
public Buffer putChar(char value) {
return put((byte) value);
}
public char getChar(int index) {
return (char) get(index);
}
public Buffer putChar(int index, char value) {
return put(index, (byte) value);
}
public ByteBuffer asByteBuffer() {
return new ByteBuffer(_wrapped);
}
public short getShort() {
return Arrays.toShort(new byte[]{get(), get()}, Endianness.Little);
}
public Buffer putShort(short value) {
return put(Arrays.fromShort(value, Endianness.Little));
}
public short getShort(int index) {
return Arrays.toShort(new byte[]{get(index), get(index + 1)}, Endianness.Little);
}
public Buffer putShort(int index, short value) {
return put(index, Arrays.fromShort(value, Endianness.Little));
}
public int getInt() {
return Arrays.toInt(new byte[]{get(), get(), get(), get()}, Endianness.Little);
}
public Buffer putInt(int value) {
return put(Arrays.fromInt(value, Endianness.Little));
}
public int getInt(int index) {
return Arrays.toInt(new byte[]{get(index), get(index + 1), get(index + 2), get(index + 3)}, Endianness.Little);
}
public Buffer putInt(int index, int value) {
return put(index, Arrays.fromInt(value, Endianness.Little));
}
public long getLong() {
return Arrays.toLong(new byte[]{get(), get(), get(), get(), get(), get(), get(), get()}, Endianness.Little);
}
public Buffer putLong(long value) {
return put(Arrays.fromLong(value, Endianness.Little));
}
public long getLong(int index) {
return Arrays.toLong(new byte[]{get(index), get(index + 1), get(index + 2), get(index + 3), get(index + 4), get(index + 5), get(index + 6), get(index + 7)}, Endianness.Little);
}
public Buffer putLong(int index, long value) {
return put(index, Arrays.fromLong(value, Endianness.Little));
}
public boolean isReadOnly() {
return _wrapped.isReadOnly();
}
public void flip() {
_wrapped.flip();
}
public int remaining() {
double rem = ((double) _wrapped.remaining() / 2.0d);
return (int) Math.floor(rem);
}
public boolean hasRemaining() {
return (_wrapped.remaining() > 1);
}
public HexBuffer asHexBuffer() {
return this;
}
}

View File

@@ -0,0 +1,6 @@
package zmodem.xfer.util;
public class InvalidChecksumException extends RuntimeException {
private static final long serialVersionUID = 3864874377147160043L;
}

View File

@@ -0,0 +1,7 @@
package zmodem.xfer.util;
/**
* Created by asirotinkin on 12.11.2014.
*/
class TimeoutException extends Exception {
}

View File

@@ -0,0 +1,10 @@
package zmodem.xfer.util;
/**
* Created by Muzeffer on 2016/6/30.
*/
public interface XCRC {
int getCRCLength();
long calcCRC(byte[] block);
}

View File

@@ -0,0 +1,29 @@
package zmodem.xfer.zm.packet;
import zmodem.xfer.util.ASCII;
import zmodem.xfer.util.Buffer;
import zmodem.xfer.util.ByteBuffer;
import zmodem.xfer.zm.util.ZMPacket;
public class Cancel extends ZMPacket {
@Override
public Buffer marshall() {
ByteBuffer buff = ByteBuffer.allocate(16);
for (int i = 0; i < 8; i++)
buff.put(ASCII.CAN.value());
for (int i = 0; i < 8; i++)
buff.put(ASCII.BS.value());
buff.flip();
return buff;
}
@Override
public String toString() {
return "Cancel: CAN * 8 + BS * 8";
}
}

View File

@@ -0,0 +1,92 @@
package zmodem.xfer.zm.packet;
import zmodem.xfer.util.*;
import zmodem.xfer.zm.util.ZDLEEncoder;
import zmodem.xfer.zm.util.ZMPacket;
import zmodem.xfer.zm.util.ZModemCharacter;
import java.io.ByteArrayOutputStream;
public class DataPacket extends ZMPacket {
public static DataPacket unmarshall(Buffer buff, CRC crc) {
byte[] data = new byte[buff.remaining() - crc.size() - 1];
buff.get(data);
ZModemCharacter type;
type = ZModemCharacter.forbyte(buff.get());
byte[] netCrc = new byte[crc.size()];
buff.get(netCrc);
if (!Arrays.equals(netCrc, crc.getBytes()))
throw new InvalidChecksumException();
return new DataPacket(type, data);
}
private final ZModemCharacter type;
private byte[] data = new byte[0];
public DataPacket(ZModemCharacter fe) {
type = fe;
}
public DataPacket(ZModemCharacter fr, byte[] d) {
this(fr);
data = d;
}
public ZModemCharacter type() {
return type;
}
public byte[] data() {
return data;
}
public void setData(byte[] d) {
data = d;
}
public void copyData(byte[] d) {
data = Arrays.copyOf(d, d.length);
}
@Override
public Buffer marshall() {
ZDLEEncoder encoder;
ByteBuffer buff = ByteBuffer.allocate(data.length * 2 + 64);
CRC crc = new CRC(CRC.Type.CRC16);
encoder = new ZDLEEncoder(data);
crc.update(data);
buff.put(encoder.zdle(), 0, encoder.zdleLen());
buff.put(ZModemCharacter.ZDLE.value());
crc.update(type.value());
buff.put(type.value());
crc.finalized();
encoder = new ZDLEEncoder(crc.getBytes());
buff.put(encoder.zdle(), 0, encoder.zdleLen());
buff.flip();
return buff;
}
@Override
public String toString() {
return type + ":" + data.length + " bytes";
}
}

View File

@@ -0,0 +1,27 @@
package zmodem.xfer.zm.packet;
import zmodem.xfer.util.Buffer;
import zmodem.xfer.util.ByteBuffer;
import zmodem.xfer.zm.util.ZMPacket;
public class Finish extends ZMPacket {
@Override
public Buffer marshall() {
ByteBuffer buff = ByteBuffer.allocate(16);
for (int i = 0; i < 2; i++)
buff.put((byte) 'O');
buff.flip();
return buff;
}
@Override
public String toString() {
return "Finish: OO";
}
}

View File

@@ -0,0 +1,48 @@
package zmodem.xfer.zm.packet;
import zmodem.xfer.util.CRC;
import zmodem.xfer.zm.util.ZModemCharacter;
public enum Format {
BIN32(1, CRC.Type.CRC32, ZModemCharacter.ZBIN32),
BIN(1, CRC.Type.CRC16, ZModemCharacter.ZBIN),
HEX(2, CRC.Type.CRC16, ZModemCharacter.ZHEX);
public static Format fromByte(byte b) {
for (Format ft : values()) {
if (ft.character() == b)
return ft;
}
return null;
}
private int width;
private CRC.Type crc;
private ZModemCharacter character;
private Format(int bw, CRC.Type crct, ZModemCharacter fmt) {
width = bw;
crc = crct;
character = fmt;
}
public CRC.Type crc() {
return crc;
}
public byte character() {
return character.value();
}
public int width() {
return width;
}
public boolean hex() {
return (this == HEX);
}
}

View File

@@ -0,0 +1,130 @@
package zmodem.xfer.zm.packet;
import zmodem.xfer.util.*;
import zmodem.xfer.zm.util.ZDLEEncoder;
import zmodem.xfer.zm.util.ZMPacket;
import zmodem.xfer.zm.util.ZModemCharacter;
public class Header extends ZMPacket {
public static Header unmarshall(Buffer buff) {
Format fmt = null;
while (fmt == null)
fmt = Format.fromByte(buff.get());
if (fmt.hex())
buff = buff.asHexBuffer();
CRC crc = new CRC(fmt.crc());
byte b;
b = buff.get();
crc.update(b);
ZModemCharacter type = ZModemCharacter.forbyte(b);
byte[] data = new byte[4];
for (int i = 0; i < data.length; i++) {
b = buff.get();
crc.update(b);
data[i] = b;
}
crc.finalized();
byte[] netCrc = new byte[crc.size()];
buff.get(netCrc);
if (!Arrays.equals(netCrc, crc.getBytes()))
throw new InvalidChecksumException();
return new Header(fmt, type, data);
}
private Format format;
private ZModemCharacter type;
private byte[] data = {0, 0, 0, 0};
private Header(Format fFmt) {
format = fFmt;
}
public Header(Format fFmt, ZModemCharacter fType) {
this(fFmt);
type = fType;
}
public Header(Format fFmt, ZModemCharacter fType, byte[] flags) {
this(fFmt, fType);
setFlags(flags);
}
public Header(Format fFmt, ZModemCharacter fType, int pos) {
this(fFmt, fType);
setPos(pos);
}
public ZModemCharacter type() {
return type;
}
public Format format() {
return format;
}
public void setFlags(byte[] flags) {
data = Arrays.copyOf(flags, flags.length);
}
public byte[] getFlags() {
return data;
}
public void setPos(int num) {
data = Arrays.fromInt(num, Arrays.Endianness.Little);
}
public int getPos() {
return Arrays.toInt(data, Arrays.Endianness.Little);
}
@Override
public Buffer marshall() {
ZDLEEncoder encoder;
Buffer buff;
if (format.hex())
buff = HexBuffer.allocate(16);
else
buff = ByteBuffer.allocate(32);
CRC crc = new CRC(format.crc());
crc.update(type.value());
buff.put(type.value());
crc.update(data);
encoder = new ZDLEEncoder(data, format);
buff.put(encoder.zdle(), 0, encoder.zdleLen());
crc.finalized();
encoder = new ZDLEEncoder(crc.getBytes(), format);
buff.put(encoder.zdle(), 0, encoder.zdleLen());
buff.flip();
return buff.asByteBuffer();
}
@Override
public String toString() {
return type + ", " + format + ", " + "{" + data[0] + "," + data[1] + "," + data[2] + "," + data[3] + "}";
}
}

View File

@@ -0,0 +1,9 @@
package zmodem.xfer.zm.packet;
import java.io.IOException;
public class InvalidPacketException extends IOException {
private static final long serialVersionUID = 6436104259898858243L;
}

View File

@@ -0,0 +1,5 @@
package zmodem.xfer.zm.proto;
public enum Action {
ESCAPE, DATA, HEADER, CANCEL, FINISH;
}

View File

@@ -0,0 +1,99 @@
package zmodem.xfer.zm.proto;
import zmodem.xfer.zm.util.ZModemCharacter;
import java.util.HashMap;
import java.util.Map;
public class Escape {
private int len = 0;
private Action action = Action.ESCAPE;
public Escape(Action a) {
this(a, 0);
}
public Escape(Action a, int l) {
len = l;
action = a;
}
public Action action() {
return action;
}
public int len() {
return len;
}
private static Map<Byte, Escape> _specials = new HashMap<Byte, Escape>();
static {
_specials.put(ZModemCharacter.ZBIN.value(), new Escape(Action.HEADER, 7));
_specials.put(ZModemCharacter.ZHEX.value(), new Escape(Action.HEADER, 16));
_specials.put(ZModemCharacter.ZBIN32.value(), new Escape(Action.HEADER, 9));
_specials.put(ZModemCharacter.ZCRCE.value(), new Escape(Action.DATA, 2));
_specials.put(ZModemCharacter.ZCRCG.value(), new Escape(Action.DATA, 2));
_specials.put(ZModemCharacter.ZCRCQ.value(), new Escape(Action.DATA, 2));
_specials.put(ZModemCharacter.ZCRCW.value(), new Escape(Action.DATA, 2));
}
public static Escape detect(byte b, boolean acceptsHeader) {
Escape r = _specials.get(b);
if (r == null || ((!acceptsHeader) && r.action() == Action.HEADER))
return new Escape(Action.ESCAPE);
return r;
}
public static boolean mustEscape(byte b, byte previous, boolean escapeCtl) {
switch (b) {
case 0xd:
case (byte) 0x8d:
if (escapeCtl && previous == '@')
return true;
break;
case 0x18:
case 0x10:
case 0x11:
case 0x13:
case (byte) 0x7f:
case (byte) 0x90:
case (byte) 0x91:
case (byte) 0x93:
case (byte) 0xff:
return true;
default:
if (escapeCtl && ((b & 0x60) == 0))
return true;
}
return false;
}
public static byte escapeIt(byte b) {
if (b == (byte) 0x7f)
return ZModemCharacter.ZRUB0.value();
if (b == (byte) 0xff)
return ZModemCharacter.ZRUB1.value();
if (b == (byte) ZModemCharacter.ZRUB0.value())
return 0x7f;
if (b == (byte) ZModemCharacter.ZRUB1.value())
return (byte) 0xff;
return (byte) (b ^ 0x40);
}
@Override
public String toString() {
return "Action=" + action + ", len=" + len;
}
}

View File

@@ -0,0 +1,401 @@
package zmodem.xfer.zm.util;
import zmodem.xfer.util.CRC16;
import zmodem.xfer.util.CRC8;
import zmodem.xfer.util.XCRC;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* This is core Modem class supporting XModem (and some extensions XModem-1K, XModem-CRC), and YModem.<br/>
* YModem support is limited (currently block 0 is ignored).<br/>
* <br/>
* Created by Anton Sirotinkin (aesirot@mail.ru), Moscow 2014 <br/>
* I hope you will find this program useful.<br/>
* You are free to use/modify the code for any purpose, but please leave a reference to me.<br/>
*/
public class Modem {
public static final byte SOH = 0x01; /* Start Of Header */
public static final byte STX = 0x02; /* Start Of Text (used like SOH but means 1024 block size) */
public static final byte EOT = 0x04; /* End Of Transmission */
public static final byte ACK = 0x06; /* ACKnowlege */
public static final byte NAK = 0x15; /* Negative AcKnowlege */
public static final byte CAN = 0x18; /* CANcel character */
public static final byte CPMEOF = 0x1A;
public static final byte ST_C = 'C';
public static final int MAXERRORS = 10;
public static final int BLOCK_TIMEOUT = 1000;
public static final int REQUEST_TIMEOUT = 3000;
public static final int WAIT_FOR_RECEIVER_TIMEOUT = 60_000;
public static final int SEND_BLOCK_TIMEOUT = 10_000;
private final InputStream inputStream;
private final OutputStream outputStream;
private final byte[] shortBlockBuffer;
private final byte[] longBlockBuffer;
/**
* Constructor
*
* @param inputStream stream for reading received data from other side
* @param outputStream stream for writing data to other side
*/
public Modem(InputStream inputStream, OutputStream outputStream) {
this.inputStream = inputStream;
this.outputStream = outputStream;
shortBlockBuffer = new byte[128];
longBlockBuffer = new byte[1024];
}
/**
* Wait for receiver request for transmission
*
* @return TRUE if receiver requested CRC-16 checksum, FALSE if 8bit checksum
* @throws IOException
*/
public boolean waitReceiverRequest() throws IOException {
int character;
while (true) {
character = readByte();
if (character == NAK)
return false;
if (character == ST_C) {
return true;
}
}
}
/**
* Send a file. <br/>
* <p>
* This method support correct thread interruption, when thread is interrupted "cancel of transmission" will be send.
* So you can move long transmission to other thread and interrupt it according to your algorithm.
*
* @param file
* @param useBlock1K
* @throws IOException
*/
public void send(Path file, boolean useBlock1K) throws IOException {
//open file
try (DataInputStream dataStream = new DataInputStream(Files.newInputStream(file))) {
boolean useCRC16 = waitReceiverRequest();
XCRC crc;
if (useCRC16)
crc = new CRC16();
else
crc = new CRC8();
byte[] block;
if (useBlock1K)
block = new byte[1024];
else
block = new byte[128];
sendDataBlocks(dataStream, 1, crc, block);
sendEOT();
}
}
public void sendDataBlocks(DataInputStream dataStream, int blockNumber, XCRC crc, byte[] block) throws IOException {
int dataLength;
while ((dataLength = dataStream.read(block)) != -1) {
sendBlock(blockNumber++, block, dataLength, crc);
}
}
public void sendEOT() throws IOException {
int errorCount = 0;
int character;
while (errorCount < 10) {
sendByte(EOT);
character = readByte();
if (character == ACK) {
return;
} else if (character == CAN) {
throw new IOException("Transmission terminated");
}
errorCount++;
}
}
public void sendBlock(int blockNumber, byte[] block, int dataLength, XCRC crc) throws IOException {
int errorCount;
int character;
if (dataLength < block.length) {
block[dataLength] = CPMEOF;
}
errorCount = 0;
while (errorCount < MAXERRORS) {
if (block.length == 1024)
outputStream.write(STX);
else //128
outputStream.write(SOH);
outputStream.write(blockNumber);
outputStream.write(~blockNumber);
outputStream.write(block);
writeCRC(block, crc);
outputStream.flush();
while (true) {
character = readByte();
if (character == ACK) {
return;
} else if (character == NAK) {
errorCount++;
break;
} else if (character == CAN) {
throw new IOException("Transmission terminated");
}
}
}
throw new IOException("Too many errors caught, abandoning transfer");
}
private void writeCRC(byte[] block, XCRC crc) throws IOException {
byte[] crcBytes = new byte[crc.getCRCLength()];
long crcValue = crc.calcCRC(block);
for (int i = 0; i < crc.getCRCLength(); i++) {
crcBytes[crc.getCRCLength() - i - 1] = (byte) ((crcValue >> (8 * i)) & 0xFF);
}
outputStream.write(crcBytes);
}
/**
* Receive file <br/>
* <p>
* This method support correct thread interruption, when thread is interrupted "cancel of transmission" will be send.
* So you can move long transmission to other thread and interrupt it according to your algorithm.
*
* @param file file path for storing
* @throws IOException
*/
public void receive(Path file, boolean useCRC16) throws IOException {
try (DataOutputStream dataOutput = new DataOutputStream(Files.newOutputStream(file))) {
int available;
// clean input stream
if ((available = inputStream.available()) > 0) {
inputStream.skip(available);
}
int character = requestTransmissionStart(useCRC16);
XCRC crc;
if (useCRC16)
crc = new CRC16();
else
crc = new CRC8();
processDataBlocks(crc, 1, character, dataOutput);
}
}
public void processDataBlocks(XCRC crc, int blockNumber, int blockInitialCharacter, DataOutputStream dataOutput) throws IOException {
// read blocks until EOT
boolean result = false;
boolean shortBlock;
byte[] block;
while (true) {
int errorCount = 0;
if (blockInitialCharacter == EOT) {
// end of transmission
sendByte(ACK);
return;
}
//read and process block
shortBlock = (blockInitialCharacter == SOH);
try {
block = readBlock(blockNumber, shortBlock, crc);
dataOutput.write(block);
blockNumber++;
errorCount = 0;
result = true;
sendByte(ACK);
} catch (InvalidBlockException e) {
errorCount++;
if (errorCount == MAXERRORS) {
interruptTransmission();
throw new IOException("Transmission aborted, error count exceeded max");
}
sendByte(NAK);
result = false;
} catch (RepeatedBlockException e) {
//thats ok, accept and wait for next block
sendByte(ACK);
} catch (SynchronizationLostException e) {
//fatal transmission error
interruptTransmission();
throw new IOException("Fatal transmission error", e);
}
//wait for next block
blockInitialCharacter = readNextBlockStart(result);
}
}
public void sendByte(byte b) throws IOException {
outputStream.write(b);
outputStream.flush();
}
/**
* Request transmission start and return first byte of "first" block from sender (block 1 for XModem, block 0 for YModem)
*
* @param useCRC16
* @return
* @throws IOException
*/
public int requestTransmissionStart(boolean useCRC16) throws IOException {
int character;
int errorCount = 0;
byte requestStartByte;
if (!useCRC16) {
requestStartByte = NAK;
} else {
requestStartByte = ST_C;
}
// wait for first block start
// request transmission start (will be repeated after 10 second timeout for 10 times)
sendByte(requestStartByte);
while (true) {
character = readByte();
if (character == SOH || character == STX) {
return character;
}
}
}
public int readNextBlockStart(boolean lastBlockResult) throws IOException {
int character;
int errorCount = 0;
while (true) {
while (true) {
character = readByte();
if (character == SOH || character == STX || character == EOT) {
return character;
}
}
// repeat last block result and wait for next block one more time
// if (++errorCount < MAXERRORS) {
// sendByte(lastBlockResult ? ACK : NAK);
// } else {
// interruptTransmission();
// throw new RuntimeException("Timeout, no data received from transmitter");
// }
}
}
private void shortSleep() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
try {
interruptTransmission();
} catch (IOException ignore) {
}
throw new RuntimeException("Transmission was interrupted", e);
}
}
/**
* send CAN to interrupt seance
*
* @throws IOException
*/
public void interruptTransmission() throws IOException {
sendByte(CAN);
sendByte(CAN);
}
public byte[] readBlock(int blockNumber, boolean shortBlock, XCRC crc) throws IOException, RepeatedBlockException, SynchronizationLostException, InvalidBlockException {
byte[] block;
if (shortBlock) {
block = shortBlockBuffer;
} else {
block = longBlockBuffer;
}
byte character;
character = readByte();
if (character == blockNumber - 1) {
// this is repeating of last block, possible ACK lost
throw new RepeatedBlockException();
}
if (character != blockNumber) {
// wrong block - fatal loss of synchronization
throw new SynchronizationLostException();
}
character = readByte();
if (character != ~blockNumber) {
throw new InvalidBlockException();
}
// data
for (int i = 0; i < block.length; i++) {
block[i] = readByte();
}
while (true) {
if (inputStream.available() >= crc.getCRCLength()) {
if (crc.calcCRC(block) != readCRC(crc)) {
throw new InvalidBlockException();
}
break;
}
shortSleep();
}
return block;
}
private long readCRC(XCRC crc) throws IOException {
long checkSumma = 0;
for (int j = 0; j < crc.getCRCLength(); j++) {
checkSumma = (checkSumma << 8) + inputStream.read();
}
return checkSumma;
}
private byte readByte() throws IOException {
while (true) {
if (inputStream.available() > 0) {
int b = inputStream.read();
return (byte) b;
}
shortSleep();
}
}
public class RepeatedBlockException extends Exception {
}
public class SynchronizationLostException extends Exception {
}
public class InvalidBlockException extends Exception {
}
}

View File

@@ -0,0 +1,58 @@
package zmodem.xfer.zm.util;
import zmodem.xfer.zm.packet.Format;
import zmodem.xfer.zm.proto.Escape;
public class ZDLEEncoder {
private byte[] raw;
private byte[] zdle;
private int zdleLen;
private Format format;
public ZDLEEncoder(byte[] data) {
this(data, Format.BIN);
}
public ZDLEEncoder(byte[] data, Format fmt) {
raw = data;
format = fmt;
zdle = new byte[raw.length * 2];
encode();
}
private void putZdle(byte b) {
zdle[zdleLen] = b;
zdleLen++;
}
private void encode() {
byte previous = 0;
for (byte b : raw) {
if ((!format.hex()) && Escape.mustEscape(b, previous, false)) {
putZdle(ZModemCharacter.ZDLE.value());
b = Escape.escapeIt(b);
}
putZdle(b);
previous = b;
}
}
public byte[] raw() {
return raw;
}
public int zdleLen() {
return zdleLen;
}
public byte[] zdle() {
return zdle;
}
}

View File

@@ -0,0 +1,49 @@
package zmodem.xfer.zm.util;
public enum ZMOptions {
CANFDX(0x01), /* Rx can send and receive true FDX */
CANOVIO(0x02), /* Rx can receive data during disk I/O */
CANBRK(0x04), /* Rx can send a break signal */
CANCRY(0x08), /* Receiver can decrypt */
CANLZW(0x10), /* Receiver can uncompress */
CANFC32(0x20), /* Receiver can use 32 bit Frame Check */
ESCCTL(0x40), /* Receiver expects ctl chars to be escaped */
ESC8(0x80), /* Receiver expects 8th bit to be escaped */
ZCBIN(0x01);
private byte value;
private ZMOptions(char b) {
value = (byte) b;
}
private ZMOptions(int b) {
value = (byte) b;
}
private ZMOptions(byte b) {
value = b;
}
public byte value() {
return value;
}
public static byte with(ZMOptions... oo) {
byte r = 0;
for (ZMOptions o : oo)
r = (byte) (r | o.value());
return r;
}
public static ZMOptions forbyte(byte b) {
for (ZMOptions zb : values()) {
if (zb.value() == b)
return zb;
}
return null;
}
}

View File

@@ -0,0 +1,9 @@
package zmodem.xfer.zm.util;
import zmodem.xfer.util.Buffer;
public abstract class ZMPacket {
public abstract Buffer marshall();
}

View File

@@ -0,0 +1,39 @@
package zmodem.xfer.zm.util;
import zmodem.xfer.zm.packet.DataPacket;
public class ZMPacketFactory {
public ZMPacketFactory() {
}
public DataPacket createZFilePacket(String pathname, long flen) {
return createZFilePacket(pathname, flen, 0, "0", 0, 0);
}
public DataPacket createZFilePacket(String pathname, long flen, long ts, String mode/*octal*/
, int remainingfiles, long remainingBytes) {
StringBuilder builder = new StringBuilder();
builder.append(pathname);
builder.append('\0');
builder.append(flen);
builder.append(' ');
builder.append(ts);
builder.append(' ');
builder.append(mode);
builder.append(' ');
builder.append('0');
builder.append(' ');
builder.append(remainingfiles);
builder.append(' ');
builder.append(remainingBytes);
builder.append('0');
return new DataPacket(ZModemCharacter.ZCRCW, builder.toString().getBytes());
}
}

View File

@@ -0,0 +1,64 @@
package zmodem.xfer.zm.util;
public enum ZModemCharacter {
ZPAD('*'),
ZDLE(0x18),
ZDLEE(ZDLE.value() ^ 0x40),
ZBIN('A'),
ZHEX('B'),
ZBIN32('C'),
ZCRCE('h'),
ZCRCG('i'),
ZCRCQ('j'),
ZCRCW('k'),
ZRUB0('l'),
ZRUB1('m'),
ZRQINIT(0),
ZRINIT(1),
ZSINIT(2),
ZACK(3),
ZFILE(4),
ZSKIP(5),
ZNAK(6),
ZABORT(7),
ZFIN(8),
ZRPOS(9),
ZDATA(10),
ZEOF(11),
ZFERR(12),
ZCRC(13),
ZCHALLENGE(14),
ZCOMPL(15),
ZCAN(16),
ZFREECNT(17),
ZCOMMAND(18),
ZSTDERR(19);
private byte value;
private ZModemCharacter(char b) {
value = (byte) b;
}
private ZModemCharacter(int b) {
value = (byte) b;
}
private ZModemCharacter(byte b) {
value = b;
}
public byte value() {
return value;
}
public static ZModemCharacter forbyte(byte b) {
for (ZModemCharacter zb : values()) {
if (zb.value() == b)
return zb;
}
return null;
}
}

View File

@@ -0,0 +1,259 @@
package zmodem.xfer.zm.util;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.net.io.CopyStreamAdapter;
import org.apache.commons.net.io.CopyStreamListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import zmodem.FileCopyStreamEvent;
import zmodem.util.EmptyFileAdapter;
import zmodem.util.FileAdapter;
import zmodem.xfer.util.InvalidChecksumException;
import zmodem.xfer.zm.packet.*;
import zmodem.zm.io.ZMPacketInputStream;
import zmodem.zm.io.ZMPacketOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.function.Supplier;
public class ZModemReceive {
private static final Logger log = LoggerFactory.getLogger(ZModemReceive.class);
private final CopyStreamAdapter adapter = new CopyStreamAdapter();
private final Supplier<FileAdapter> destinationSupplier;
private FileAdapter destination;
private FileAdapter file;
private int fOffset = 0;
private Long filesize;
private int remaining = 0;
private int index = 0;
private OutputStream fileOs = null;
private final InputStream netIs;
private final OutputStream netOs;
private enum Expect {
FILENAME, DATA, NOTHING;
}
public ZModemReceive(Supplier<FileAdapter> destDir, InputStream netin, OutputStream netout) throws IOException {
destinationSupplier = destDir;
netIs = netin;
netOs = netout;
}
private void open(int offset) throws IOException {
boolean append = false;
if (offset != 0) {
if (file.exists() && file.length() == offset)
append = true;
else
offset = 0;
}
IOUtils.closeQuietly(fileOs);
fileOs = file.getOutputStream(append);
fOffset = offset;
}
private void decodeFileNameData(DataPacket p) {
ByteArrayOutputStream filename = new ByteArrayOutputStream();
StringBuilder extract = new StringBuilder();
byte[] data = p.data();
for (int i = 0; i < data.length; i++) {
byte b = data[i];
if (b == 0) {
for (int j = i + 1; j < data.length; j++) {
b = data[j];
if (b == 0) {
break;
}
extract.append((char) b);
}
break;
}
filename.write(b);
}
final String[] segments = extract.toString().split(StringUtils.SPACE);
if (ArrayUtils.isNotEmpty(segments)) {
// filesize
if (segments.length >= 1) {
this.filesize = NumberUtils.toLong(segments[0]);
}
// remaining
if (segments.length >= 5) {
this.remaining = NumberUtils.toInt(segments[4]);
}
}
file = destination.getChild(filename.toString());
fOffset = 0;
index++;
adapter.bytesTransferred(new FileCopyStreamEvent(this, file.getName(), remaining - index, index,
this.filesize, fOffset, 0, false));
}
public void addCopyStreamListener(CopyStreamListener listener) {
adapter.addCopyStreamListener(listener);
}
public void removeCopyStreamListener(CopyStreamListener listener) {
adapter.removeCopyStreamListener(listener);
}
private void writeData(DataPacket p) throws IOException {
final byte[] data = p.data();
fileOs.write(data);
fOffset += data.length;
// 开始传输
adapter.bytesTransferred(new FileCopyStreamEvent(this, file.getName(), remaining, index,
this.filesize, fOffset, 0, false));
}
private boolean initDestination() {
if (destination != null) {
return true;
}
destination = destinationSupplier.get();
return !(destination instanceof EmptyFileAdapter);
}
public void receive(Supplier<Boolean> isCancelled) {
ZMPacketInputStream is = new ZMPacketInputStream(netIs);
ZMPacketOutputStream os = new ZMPacketOutputStream(netOs);
Expect expect = Expect.NOTHING;
byte[] recvOpt = {0, 4, 0, ZMOptions.with(ZMOptions.ESCCTL, ZMOptions.ESC8)};
try {
boolean end = false;
int errorCount = 0;
ZMPacket packet = null;
while (!end) {
try {
packet = is.read();
} catch (InvalidChecksumException ice) {
if (log.isErrorEnabled()) {
log.error(ice.getMessage(), ice);
}
++errorCount;
if (errorCount >= 3) {
os.write(new Cancel());
end = true;
}
}
if (packet instanceof Cancel) {
end = true;
} else if (packet instanceof Finish) {
end = true;
}
if (isCancelled.get()) {
break;
}
// 如果重定向为空,则终止传输
if (destination instanceof EmptyFileAdapter) {
os.write(new Cancel());
break;
}
if (packet instanceof Header header) {
switch (header.type()) {
case ZRQINIT:
os.write(new Header(Format.HEX, ZModemCharacter.ZRINIT, recvOpt));
break;
case ZFILE:
expect = Expect.FILENAME;
break;
case ZEOF:
os.write(new Header(Format.HEX, ZModemCharacter.ZRINIT, recvOpt));
expect = Expect.NOTHING;
file = null;
fileOs.flush();
IOUtils.closeQuietly(fileOs);
fileOs = null;
break;
case ZDATA:
open(header.getPos());
expect = Expect.DATA;
break;
case ZFIN:
os.write(new Header(Format.HEX, ZModemCharacter.ZFIN));
end = true;
break;
default:
end = true;
os.write(new Cancel());
break;
}
}
if (packet instanceof DataPacket data) {
switch (expect) {
case NOTHING:
os.write(new Header(Format.HEX, ZModemCharacter.ZRINIT, recvOpt));
break;
case FILENAME:
if (!initDestination()) {
end = true;
os.write(new Cancel());
break;
}
decodeFileNameData(data);
if (file.length() == filesize) {
os.write(new Header(Format.HEX, ZModemCharacter.ZSKIP));
adapter.bytesTransferred(new FileCopyStreamEvent(this, file.getName(), remaining, index,
this.filesize, fOffset, 0, true));
} else {
os.write(new Header(Format.HEX, ZModemCharacter.ZRPOS, (int) file.length()));
}
expect = Expect.NOTHING;
break;
case DATA:
writeData(data);
switch (data.type()) {
case ZCRCW:
expect = Expect.NOTHING;
case ZCRCQ:
os.write(new Header(Format.HEX, ZModemCharacter.ZACK, fOffset));
break;
case ZCRCE:
expect = Expect.NOTHING;
break;
}
}
}
}
} catch (IOException e) {
if (log.isErrorEnabled()) {
log.error(e.getMessage(), e);
}
} finally {
IOUtils.closeQuietly(fileOs);
}
}
}

View File

@@ -0,0 +1,213 @@
package zmodem.xfer.zm.util;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.net.io.CopyStreamAdapter;
import org.apache.commons.net.io.CopyStreamListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import zmodem.FileCopyStreamEvent;
import zmodem.util.FileAdapter;
import zmodem.xfer.util.InvalidChecksumException;
import zmodem.xfer.zm.packet.*;
import zmodem.zm.io.ZMPacketInputStream;
import zmodem.zm.io.ZMPacketOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;
import java.util.function.Supplier;
public class ZModemSend {
private static final int packLen = 1024 * 8;
private static final Logger log = LoggerFactory.getLogger(ZModemSend.class);
private final byte[] data = new byte[packLen];
private final CopyStreamAdapter adapter = new CopyStreamAdapter();
private final Supplier<List<FileAdapter>> destinationSupplier;
private final InputStream netIs;
private final OutputStream netOs;
private List<FileAdapter> files;
private Iterator<FileAdapter> iter;
private FileAdapter file;
private int fOffset = 0;
private int index = 0;
private int filesize = 0;
private boolean atEof = false;
private InputStream fileIs;
public ZModemSend(Supplier<List<FileAdapter>> destinationSupplier, InputStream netin, OutputStream netout) throws IOException {
this.destinationSupplier = destinationSupplier;
netIs = netin;
netOs = netout;
}
public boolean nextFile() throws IOException {
IOUtils.closeQuietly(fileIs);
if (files == null) {
files = destinationSupplier.get();
iter = files.iterator();
}
if (!iter.hasNext())
return false;
file = iter.next();
fileIs = file.getInputStream();
filesize = fileIs.available();
fOffset = 0;
atEof = false;
index++;
return true;
}
public void addCopyStreamListener(CopyStreamListener listener) {
adapter.addCopyStreamListener(listener);
}
public void removeCopyStreamListener(CopyStreamListener listener) {
adapter.removeCopyStreamListener(listener);
}
private void position(int offset) throws IOException {
if (offset != fOffset) {
fileIs.skipNBytes(offset);
fOffset = offset;
}
}
private byte[] getNextBlock() throws IOException {
final int len = fileIs.read(data);
/* we know it is a file: all the data is locally available.*/
if (len < data.length)
atEof = true;
else if (fileIs.available() == 0)
atEof = true;
if (len == -1) {
return null;
}
fOffset += len;
if (len != data.length)
return ArrayUtils.subarray(data, 0, len);
else
return data;
}
private DataPacket getNextDataPacket() throws IOException {
byte[] data = getNextBlock();
ZModemCharacter fe = ZModemCharacter.ZCRCW;
if (atEof) {
fe = ZModemCharacter.ZCRCE;
fileIs.close();
}
if (data == null) {
return new DataPacket(fe);
}
return new DataPacket(fe, data);
}
public void send(Supplier<Boolean> isCancelled) {
ZMPacketFactory factory = new ZMPacketFactory();
ZMPacketInputStream is = new ZMPacketInputStream(netIs);
ZMPacketOutputStream os = new ZMPacketOutputStream(netOs);
try {
boolean end = false;
int errorCount = 0;
ZMPacket packet = null;
while (!end) {
try {
packet = is.read();
} catch (InvalidChecksumException ice) {
++errorCount;
if (errorCount > 20) {
os.write(new Cancel());
end = true;
}
}
if (packet instanceof Cancel) {
end = true;
} else if (isCancelled.get()) {
os.write(new Cancel());
continue;
}
if (packet instanceof Header header) {
switch (header.type()) {
case ZSKIP:
fireBytesTransferred(true);
case ZRINIT:
if (!nextFile()) {
os.write(new Header(Format.BIN, ZModemCharacter.ZFIN));
} else {
os.write(new Header(Format.BIN, ZModemCharacter.ZFILE, new byte[]{0, 0, 0, ZMOptions.with(ZMOptions.ZCBIN)}));
os.write(factory.createZFilePacket(file.getName(), filesize));
fireBytesTransferred(false);
}
break;
case ZRPOS:
if (!atEof)
position(header.getPos());
case ZACK:
os.write(new Header(Format.BIN, ZModemCharacter.ZDATA, fOffset));
os.write(getNextDataPacket());
if (atEof) {
os.write(new Header(Format.HEX, ZModemCharacter.ZEOF, fOffset));
}
fireBytesTransferred(false);
break;
case ZFIN:
end = true;
os.write(new Finish());
break;
default:
end = true;
os.write(new Cancel());
break;
}
}
}
} catch (IOException e) {
if (log.isErrorEnabled()) {
log.error(e.getMessage(), e);
}
} finally {
IOUtils.closeQuietly(fileIs);
}
}
private void fireBytesTransferred(boolean skip) {
if (this.filesize == fOffset) {
System.out.println();
}
adapter.bytesTransferred(new FileCopyStreamEvent(this, file.getName(), files.size() - index + 1, index,
this.filesize, fOffset, 0, skip));
}
}

View File

@@ -0,0 +1,145 @@
package zmodem.zm.io;
import zmodem.xfer.io.ObjectInputStream;
import zmodem.xfer.util.ByteBuffer;
import zmodem.xfer.util.CRC;
import zmodem.xfer.zm.packet.*;
import zmodem.xfer.zm.proto.Action;
import zmodem.xfer.zm.proto.Escape;
import zmodem.xfer.zm.util.ZMPacket;
import zmodem.xfer.zm.util.ZModemCharacter;
import java.io.IOException;
import java.io.InputStream;
public class ZMPacketInputStream extends ObjectInputStream<ZMPacket> {
private final InputStream netIs;
private CRC dataCRC = new CRC(CRC.Type.CRC16);
private boolean gotFIN = false;
private boolean acceptsHeader = true;
public ZMPacketInputStream(InputStream is) {
netIs = is;
}
private boolean ignored(int b) {
return b == 0x11 || b == 0x13 || b == 0x91 || b == 0x93;
}
private byte implRead() throws IOException {
int n;
do {
n = netIs.read();
} while (ignored(n));
if (n == -1) {
throw new IOException("Closed");
}
return (byte) n;
}
@Override
public ZMPacket read() throws IOException {
ByteBuffer zbuff = ByteBuffer.allocate(1024 * 10);
boolean doread = true;
Action action = Action.ESCAPE;
int beforeStop = -1;
int countCan = 0;
while (doread) {
byte n = implRead();
if (gotFIN && n == 'O') {
n = implRead();
if (n == 'O') {
return new Finish();
}
}
if (n == ZModemCharacter.ZDLE.value()) {
n = (byte) netIs.read();
if (n == ZModemCharacter.ZDLE.value())
countCan += 2;
else
countCan = 0;
Escape escape = Escape.detect(n, acceptsHeader);
if (escape.action() != Action.ESCAPE && beforeStop < 0) {
action = escape.action();
if (escape.action() == Action.DATA)
beforeStop = dataCRC.size();
else
beforeStop = escape.len();
dataCRC.update(n);
} else {
n = Escape.escapeIt(n);
}
}
zbuff.put(n);
if (beforeStop < 0)
dataCRC.update(n);
if (beforeStop == 0)
doread = false;
if (beforeStop > 0)
beforeStop--;
if (countCan >= 5) {
doread = false;
action = Action.CANCEL;
}
}
zbuff.flip();
ZMPacket r = null;
switch (action) {
case HEADER:
r = Header.unmarshall(zbuff);
if (((Header) r).format() == Format.BIN32)
dataCRC = new CRC(CRC.Type.CRC32);
else
dataCRC = new CRC(CRC.Type.CRC16);
if (((Header) r).type() == ZModemCharacter.ZFIN)
gotFIN = true;
if (((Header) r).type() == ZModemCharacter.ZDATA || ((Header) r).type() == ZModemCharacter.ZFILE)
acceptsHeader = false;
break;
case DATA:
dataCRC.finalized();
r = DataPacket.unmarshall(zbuff, dataCRC);
dataCRC = new CRC(dataCRC.type());
if (((DataPacket) r).type() == ZModemCharacter.ZCRCG)
acceptsHeader = false;
else
acceptsHeader = true;
break;
case CANCEL:
r = new Cancel();
dataCRC = new CRC(dataCRC.type());
break;
}
return r;
}
}

View File

@@ -0,0 +1,67 @@
package zmodem.zm.io;
import zmodem.xfer.io.ObjectOutputStream;
import zmodem.xfer.util.ASCII;
import zmodem.xfer.util.Buffer;
import zmodem.xfer.zm.packet.DataPacket;
import zmodem.xfer.zm.packet.Format;
import zmodem.xfer.zm.packet.Header;
import zmodem.xfer.zm.util.ZMPacket;
import zmodem.xfer.zm.util.ZModemCharacter;
import java.io.IOException;
import java.io.OutputStream;
public class ZMPacketOutputStream extends ObjectOutputStream<ZMPacket> {
private final OutputStream os;
public ZMPacketOutputStream(OutputStream netOs) {
os = netOs;
}
public void implWrite(byte b) throws IOException {
//System.out.printf("%02x",b);
os.write(b);
}
@Override
public void write(ZMPacket o) throws IOException {
Buffer buff = o.marshall();
Format fmt = null;
if (o instanceof Header)
fmt = ((Header) o).format();
if (fmt != null) {
for (int i = 0; i < fmt.width(); i++)
implWrite(ZModemCharacter.ZPAD.value());
implWrite(ZModemCharacter.ZDLE.value());
implWrite(fmt.character());
}
if (buff.hasRemaining()) {
byte[] buf = new byte[buff.remaining()];
buff.get(buf);
os.write(buf);
}
if (fmt != null) if (fmt.hex()) {
implWrite(ASCII.CR.value());
implWrite(ASCII.LF.value());
implWrite(ASCII.XON.value());
}
if (o instanceof DataPacket) if (((DataPacket) o).type() == ZModemCharacter.ZCRCW)
implWrite(ASCII.XON.value());
os.flush();
}
}