mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 10:22:58 +08:00
Init Commit
This commit is contained in:
14
src/main/java/app/termora/Disposable.java
Normal file
14
src/main/java/app/termora/Disposable.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
192
src/main/java/app/termora/Disposer.java
Normal file
192
src/main/java/app/termora/Disposer.java
Normal 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();
|
||||
}
|
||||
}
|
||||
128
src/main/java/app/termora/ObjectNode.java
Normal file
128
src/main/java/app/termora/ObjectNode.java
Normal 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;
|
||||
}
|
||||
}
|
||||
246
src/main/java/app/termora/ObjectTree.java
Normal file
246
src/main/java/app/termora/ObjectTree.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/main/java/zmodem/FileCopyStreamEvent.kt
Normal file
34
src/main/java/zmodem/FileCopyStreamEvent.kt
Normal 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
|
||||
)
|
||||
24
src/main/java/zmodem/XModem.java
Normal file
24
src/main/java/zmodem/XModem.java
Normal 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);
|
||||
}
|
||||
}
|
||||
207
src/main/java/zmodem/YModem.java
Normal file
207
src/main/java/zmodem/YModem.java
Normal 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;
|
||||
}
|
||||
}
|
||||
46
src/main/java/zmodem/ZModem.java
Normal file
46
src/main/java/zmodem/ZModem.java
Normal 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);
|
||||
}
|
||||
}
|
||||
4
src/main/java/zmodem/package-info.java
Normal file
4
src/main/java/zmodem/package-info.java
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* https://github.com/scraymer/Zmodem-in-Java
|
||||
*/
|
||||
package zmodem;
|
||||
65
src/main/java/zmodem/util/CustomFile.java
Normal file
65
src/main/java/zmodem/util/CustomFile.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
42
src/main/java/zmodem/util/EmptyFileAdapter.kt
Normal file
42
src/main/java/zmodem/util/EmptyFileAdapter.kt
Normal 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
|
||||
}
|
||||
}
|
||||
25
src/main/java/zmodem/util/FileAdapter.java
Normal file
25
src/main/java/zmodem/util/FileAdapter.java
Normal 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();
|
||||
}
|
||||
7
src/main/java/zmodem/xfer/io/ObjectInputStream.java
Normal file
7
src/main/java/zmodem/xfer/io/ObjectInputStream.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package zmodem.xfer.io;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public abstract class ObjectInputStream<T> {
|
||||
public abstract T read() throws IOException;
|
||||
}
|
||||
7
src/main/java/zmodem/xfer/io/ObjectOutputStream.java
Normal file
7
src/main/java/zmodem/xfer/io/ObjectOutputStream.java
Normal 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;
|
||||
}
|
||||
27
src/main/java/zmodem/xfer/util/ASCII.java
Normal file
27
src/main/java/zmodem/xfer/util/ASCII.java
Normal 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;
|
||||
}
|
||||
}
|
||||
91
src/main/java/zmodem/xfer/util/Arrays.java
Normal file
91
src/main/java/zmodem/xfer/util/Arrays.java
Normal 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);
|
||||
}
|
||||
}
|
||||
38
src/main/java/zmodem/xfer/util/Buffer.java
Normal file
38
src/main/java/zmodem/xfer/util/Buffer.java
Normal 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();
|
||||
|
||||
}
|
||||
193
src/main/java/zmodem/xfer/util/ByteBuffer.java
Normal file
193
src/main/java/zmodem/xfer/util/ByteBuffer.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
232
src/main/java/zmodem/xfer/util/CRC.java
Normal file
232
src/main/java/zmodem/xfer/util/CRC.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
58
src/main/java/zmodem/xfer/util/CRC16.java
Normal file
58
src/main/java/zmodem/xfer/util/CRC16.java
Normal 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;
|
||||
}
|
||||
}
|
||||
21
src/main/java/zmodem/xfer/util/CRC8.java
Normal file
21
src/main/java/zmodem/xfer/util/CRC8.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
235
src/main/java/zmodem/xfer/util/HexBuffer.java
Normal file
235
src/main/java/zmodem/xfer/util/HexBuffer.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package zmodem.xfer.util;
|
||||
|
||||
public class InvalidChecksumException extends RuntimeException {
|
||||
private static final long serialVersionUID = 3864874377147160043L;
|
||||
|
||||
}
|
||||
7
src/main/java/zmodem/xfer/util/TimeoutException.java
Normal file
7
src/main/java/zmodem/xfer/util/TimeoutException.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package zmodem.xfer.util;
|
||||
|
||||
/**
|
||||
* Created by asirotinkin on 12.11.2014.
|
||||
*/
|
||||
class TimeoutException extends Exception {
|
||||
}
|
||||
10
src/main/java/zmodem/xfer/util/XCRC.java
Normal file
10
src/main/java/zmodem/xfer/util/XCRC.java
Normal 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);
|
||||
}
|
||||
29
src/main/java/zmodem/xfer/zm/packet/Cancel.java
Normal file
29
src/main/java/zmodem/xfer/zm/packet/Cancel.java
Normal 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";
|
||||
}
|
||||
}
|
||||
92
src/main/java/zmodem/xfer/zm/packet/DataPacket.java
Normal file
92
src/main/java/zmodem/xfer/zm/packet/DataPacket.java
Normal 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";
|
||||
}
|
||||
}
|
||||
27
src/main/java/zmodem/xfer/zm/packet/Finish.java
Normal file
27
src/main/java/zmodem/xfer/zm/packet/Finish.java
Normal 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";
|
||||
}
|
||||
|
||||
}
|
||||
48
src/main/java/zmodem/xfer/zm/packet/Format.java
Normal file
48
src/main/java/zmodem/xfer/zm/packet/Format.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
130
src/main/java/zmodem/xfer/zm/packet/Header.java
Normal file
130
src/main/java/zmodem/xfer/zm/packet/Header.java
Normal 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] + "}";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package zmodem.xfer.zm.packet;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class InvalidPacketException extends IOException {
|
||||
|
||||
private static final long serialVersionUID = 6436104259898858243L;
|
||||
|
||||
}
|
||||
5
src/main/java/zmodem/xfer/zm/proto/Action.java
Normal file
5
src/main/java/zmodem/xfer/zm/proto/Action.java
Normal file
@@ -0,0 +1,5 @@
|
||||
package zmodem.xfer.zm.proto;
|
||||
|
||||
public enum Action {
|
||||
ESCAPE, DATA, HEADER, CANCEL, FINISH;
|
||||
}
|
||||
99
src/main/java/zmodem/xfer/zm/proto/Escape.java
Normal file
99
src/main/java/zmodem/xfer/zm/proto/Escape.java
Normal 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;
|
||||
}
|
||||
}
|
||||
401
src/main/java/zmodem/xfer/zm/util/Modem.java
Normal file
401
src/main/java/zmodem/xfer/zm/util/Modem.java
Normal 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 {
|
||||
}
|
||||
}
|
||||
58
src/main/java/zmodem/xfer/zm/util/ZDLEEncoder.java
Normal file
58
src/main/java/zmodem/xfer/zm/util/ZDLEEncoder.java
Normal 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
49
src/main/java/zmodem/xfer/zm/util/ZMOptions.java
Normal file
49
src/main/java/zmodem/xfer/zm/util/ZMOptions.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
9
src/main/java/zmodem/xfer/zm/util/ZMPacket.java
Normal file
9
src/main/java/zmodem/xfer/zm/util/ZMPacket.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package zmodem.xfer.zm.util;
|
||||
|
||||
|
||||
import zmodem.xfer.util.Buffer;
|
||||
|
||||
public abstract class ZMPacket {
|
||||
public abstract Buffer marshall();
|
||||
|
||||
}
|
||||
39
src/main/java/zmodem/xfer/zm/util/ZMPacketFactory.java
Normal file
39
src/main/java/zmodem/xfer/zm/util/ZMPacketFactory.java
Normal 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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
64
src/main/java/zmodem/xfer/zm/util/ZModemCharacter.java
Normal file
64
src/main/java/zmodem/xfer/zm/util/ZModemCharacter.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
259
src/main/java/zmodem/xfer/zm/util/ZModemReceive.java
Normal file
259
src/main/java/zmodem/xfer/zm/util/ZModemReceive.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
213
src/main/java/zmodem/xfer/zm/util/ZModemSend.java
Normal file
213
src/main/java/zmodem/xfer/zm/util/ZModemSend.java
Normal 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));
|
||||
}
|
||||
}
|
||||
145
src/main/java/zmodem/zm/io/ZMPacketInputStream.java
Normal file
145
src/main/java/zmodem/zm/io/ZMPacketInputStream.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
67
src/main/java/zmodem/zm/io/ZMPacketOutputStream.java
Normal file
67
src/main/java/zmodem/zm/io/ZMPacketOutputStream.java
Normal 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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user