diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/Pair.java b/bramble-api/src/main/java/org/briarproject/bramble/api/Pair.java
index cd81f1adce28cbf5f908b9bb649d61775a8271ff..10d53d267e6ca87f46c5a93012919d7186e7c2f0 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/Pair.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/Pair.java
@@ -23,4 +23,8 @@ public class Pair<A, B> {
 	public B getSecond() {
 		return second;
 	}
+
+	public String toString() {
+		return "<"+first.toString()+", "+second.toString()+">";
+	}
 }
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/LoRaConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/LoRaConstants.java
new file mode 100644
index 0000000000000000000000000000000000000000..57955c17095afd515a97f9623f914677829263d8
--- /dev/null
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/LoRaConstants.java
@@ -0,0 +1,19 @@
+package org.briarproject.bramble.api.plugin;
+
+import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
+
+public interface LoRaConstants {
+	TransportId ID = new TransportId("org.briarproject.bramble.lora");
+
+	int PKG_SIZE = 255;
+	int PAYLOAD_SIZE = PKG_SIZE-TAG_LENGTH; //239
+
+	int MAX_LATENCY = 5*60*60*1000; //5 Hours - since a big Message can fill up the Tx-Queue quite fast
+	int MAX_IDLE_TIME = Integer.MAX_VALUE;
+
+	//TODO: Do we use Backoff?
+	int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
+	int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
+	double BACKOFF_BASE = 1.2;
+
+}
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/OutgoingKeys.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/OutgoingKeys.java
index 0856b877394e636b0a73af9da7580fe95d221fca..cf6f9944ffcbc8b3b2433128e7203c0b5a447f8c 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/OutgoingKeys.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/OutgoingKeys.java
@@ -17,6 +17,10 @@ public class OutgoingKeys {
 	private final long timePeriod, streamCounter;
 	private final boolean active;
 
+	public String toString() {
+		return "timeperiod "+timePeriod+" streamCounter "+streamCounter+" active:"+active+" (keys omnitted)";
+	}
+
 	public OutgoingKeys(SecretKey tagKey, SecretKey headerKey,
 			long timePeriod, boolean active) {
 		this(tagKey, headerKey, timePeriod, 0, active);
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/StreamContext.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/StreamContext.java
index 7ef48102399074cce5131b942dea024f9a42c76f..78d2f3f8ef4f4184cfcdd32f5778b0c10ac084cd 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/StreamContext.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/StreamContext.java
@@ -6,6 +6,9 @@ import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.api.plugin.TransportId;
 import org.briarproject.nullsafety.NotNullByDefault;
 
+import java.util.logging.Logger;
+import org.briarproject.bramble.util.ByteUtils;
+
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 
@@ -15,6 +18,9 @@ import static org.briarproject.nullsafety.NullSafety.requireExactlyOneNull;
 @NotNullByDefault
 public class StreamContext {
 
+	private static final Logger LOG =
+			Logger.getLogger(StreamContext.class.getName());
+
 	@Nullable
 	private final ContactId contactId;
 	@Nullable
@@ -24,6 +30,13 @@ public class StreamContext {
 	private final long streamNumber;
 	private final boolean handshakeMode;
 
+	public String toString() {
+		if(contactId != null)
+			return "contactId "+contactId.getInt()/*+" transportId "+transportId*/+" tagKey "+ByteUtils.bytesToHex(tagKey.getBytes())+" headerKey "+ByteUtils.bytesToHex(headerKey.getBytes())+" streamNr "+streamNumber+" handshakeMode "+handshakeMode;
+		else
+			return "contactid=null";
+	}
+
 	public StreamContext(@Nullable ContactId contactId,
 			@Nullable PendingContactId pendingContactId,
 			TransportId transportId, SecretKey tagKey, SecretKey headerKey,
@@ -36,6 +49,8 @@ public class StreamContext {
 		this.headerKey = headerKey;
 		this.streamNumber = streamNumber;
 		this.handshakeMode = handshakeMode;
+
+		LOG.info("new StreamCtx("+this);
 	}
 
 	@Nullable
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportKeySet.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportKeySet.java
index 81bef3a3915205083999a850bc00e7ef0eb7aed9..6bb14b78062e27df8ce605e210aa28ea70039c3b 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportKeySet.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportKeySet.java
@@ -61,4 +61,10 @@ public class TransportKeySet {
 		return o instanceof TransportKeySet &&
 				keySetId.equals(((TransportKeySet) o).keySetId);
 	}
+
+	public String toString() {
+		if(contactId == null)
+			return "Keys for PendingContact: "+keys;
+		return "Keys contact"+contactId.getInt()+": "+keys;
+	}
 }
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportKeys.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportKeys.java
index 60c98b174ff825e452df90ec85da4a08df7ba7a3..568534f1de4a39108a56c29aff348707231b2f70 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportKeys.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportKeys.java
@@ -22,6 +22,10 @@ public class TransportKeys {
 	private final SecretKey rootKey;
 	private final boolean alice;
 
+	public String toString() {
+		return "transport "+transportId+", a lot omnitted, currOutGoingKey: "+outCurr;
+	}
+
 	/**
 	 * Constructor for rotation mode.
 	 */
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/util/ByteUtils.java b/bramble-api/src/main/java/org/briarproject/bramble/util/ByteUtils.java
index 61b2378292e8389465e970fc2aa662642ad2412a..f91fd768a183cfa0c541d604796713b590f97cb2 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/util/ByteUtils.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/util/ByteUtils.java
@@ -1,4 +1,5 @@
 package org.briarproject.bramble.util;
+import java.lang.System;
 
 public class ByteUtils {
 
@@ -93,4 +94,27 @@ public class ByteUtils {
 		if (dest >= 1 << bits) throw new AssertionError();
 		return dest;
 	}
+
+	public static byte[] concat(byte a1[], byte a2[]) {
+		if(a1 == null)
+			return a2;
+		if(a2 == null)
+			return a1;
+		
+		byte ret[] = new byte[a1.length+a2.length];
+		System.arraycopy(a1, 0, ret, 0, a1.length);
+		System.arraycopy(a2, 0, ret, a1.length, a2.length);
+		return ret;
+	}
+	
+    public static String bytesToHex(byte in[]) {
+    	final char hexDigit[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
+		
+		char outArr[] = new char[in.length*2];
+        for(int i=0; i<in.length; i++) {
+            outArr[i*2] = hexDigit[(in[i]>>>4)&0xF];
+            outArr[i*2+1] = hexDigit[in[i]&0xF];
+        }
+        return new String(outArr);
+    }
 }
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/connection/IncomingSimplexSyncConnection.java b/bramble-core/src/main/java/org/briarproject/bramble/connection/IncomingSimplexSyncConnection.java
index 705661d6ae84cdc6fc6d50361a4a1b89e85b7819..fa93ef672aba4e404376e153092270eba592d142 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/connection/IncomingSimplexSyncConnection.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/connection/IncomingSimplexSyncConnection.java
@@ -14,6 +14,7 @@ import org.briarproject.bramble.api.transport.StreamContext;
 import org.briarproject.bramble.api.transport.StreamReaderFactory;
 import org.briarproject.bramble.api.transport.StreamWriterFactory;
 import org.briarproject.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.util.ByteUtils;
 
 import java.io.IOException;
 
@@ -54,6 +55,7 @@ class IncomingSimplexSyncConnection extends SyncConnection implements Runnable {
 		StreamContext ctx;
 		try {
 			tag = readTag(reader.getInputStream());
+			LOG.info("try to recognize tag "+ByteUtils.bytesToHex(tag));
 			// If we have a tag controller, defer marking the tag as recognised
 			if (tagController == null) {
 				ctx = keyManager.getStreamContext(transportId, tag);
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterImpl.java
index 55224c729858993c8e954e10569ba1f3c6c03e7c..82b3905e5fc2a27a80964666eb4ff3d68384a990 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterImpl.java
@@ -7,6 +7,7 @@ import org.briarproject.nullsafety.NotNullByDefault;
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.Arrays;
 import java.security.GeneralSecurityException;
 
 import javax.annotation.Nullable;
@@ -24,10 +25,13 @@ import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_H
 import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_PLAINTEXT_LENGTH;
 import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
 import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
+import java.util.logging.Logger;
 
 @NotThreadSafe
 @NotNullByDefault
 class StreamEncrypterImpl implements StreamEncrypter {
+	private static final Logger LOG =
+			Logger.getLogger(StreamEncrypterImpl.class.getName());
 
 	private final OutputStream out;
 	private final AuthenticatedCipher cipher;
@@ -74,6 +78,8 @@ class StreamEncrypterImpl implements StreamEncrypter {
 		if (writeTag) writeTag();
 		// Write the stream header if required
 		if (writeStreamHeader) writeStreamHeader();
+
+		LOG.info("Payload:"+ByteUtils.bytesToHex(Arrays.copyOfRange(payload,0,payloadLength))+"; Final:"+finalFrame);
 		// Encode the frame header
 		FrameEncoder.encodeHeader(frameHeader, finalFrame, payloadLength,
 				paddingLength);
@@ -110,6 +116,7 @@ class StreamEncrypterImpl implements StreamEncrypter {
 	}
 
 	private void writeTag() throws IOException {
+		LOG.info("write Tag");
 		if (tag == null) throw new IllegalStateException();
 		out.write(tag, 0, tag.length);
 		writeTag = false;
@@ -123,6 +130,7 @@ class StreamEncrypterImpl implements StreamEncrypter {
 				INT_16_BYTES);
 		System.arraycopy(frameKey.getBytes(), 0, streamHeaderPlaintext,
 				INT_16_BYTES + INT_64_BYTES, SecretKey.LENGTH);
+		LOG.info("write Header (Protocol:"+PROTOCOL_VERSION+", streamNumber:"+streamNumber+"), ...");
 		byte[] streamHeaderCiphertext = new byte[STREAM_HEADER_LENGTH];
 		System.arraycopy(streamHeaderNonce, 0, streamHeaderCiphertext, 0,
 				STREAM_HEADER_NONCE_LENGTH);
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/AbstractLoRaPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/AbstractLoRaPlugin.java
new file mode 100644
index 0000000000000000000000000000000000000000..7bd852d6a07ad78d34a69f97ecd73dd91e0556c6
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/AbstractLoRaPlugin.java
@@ -0,0 +1,174 @@
+package org.briarproject.bramble.plugin.lora;
+
+import org.briarproject.bramble.plugin.lora.DeviceManager;
+import org.briarproject.bramble.plugin.lora.L2PWriter;
+import org.briarproject.bramble.plugin.lora.L2PReader;
+import org.briarproject.bramble.plugin.lora.L2PReaderFactory;
+import org.briarproject.bramble.api.Pair;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.api.plugin.ConnectionHandler;
+import org.briarproject.bramble.api.plugin.PluginCallback;
+import org.briarproject.bramble.api.plugin.Backoff;
+import org.briarproject.bramble.api.plugin.TransportConnectionReader;
+import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
+import org.briarproject.bramble.api.plugin.TransportId;
+import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
+import org.briarproject.bramble.api.properties.TransportProperties;
+import org.briarproject.bramble.api.system.Clock;
+import org.briarproject.bramble.api.plugin.PluginException;
+import org.briarproject.bramble.api.lifecycle.IoExecutor;
+import java.util.concurrent.Executor;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.logging.Logger;
+
+import javax.annotation.concurrent.Immutable;
+
+import static java.util.Collections.singletonMap;
+import static java.util.logging.Level.WARNING;
+import static java.util.logging.Logger.getLogger;
+import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
+import static org.briarproject.bramble.api.plugin.LoRaConstants.ID;
+import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_SUPPORTED; //TODO
+import static org.briarproject.bramble.util.LogUtils.logException;
+
+@Immutable
+@NotNullByDefault
+abstract class AbstractLoRaPlugin implements SimplexPlugin/*, EventHandler, EventListener*/ {
+
+	private static final Logger LOG =
+			getLogger(AbstractLoRaPlugin.class.getName());
+
+	protected final Clock clock;
+	protected final Backoff backoff; //TODO: Is this needed?
+	protected final long maxLatency;
+	protected final PluginCallback callback;
+	protected final int maxIdleTime;
+	protected DeviceManager devicemanager;
+	protected final Executor ioExecutor, wakefulIoExecutor;
+	protected L2PReaderFactory rf;
+
+	AbstractLoRaPlugin(
+		Executor ioExecutor,
+		Executor wakefulIoExecutor, Clock clock, Backoff backoff, PluginCallback callback, long maxLatency, int maxIdleTime) {
+		LOG.info("AbstractLoRaPlugin::Constructor");
+		this.clock = clock;
+		this.callback = callback;
+		this.backoff = backoff;
+		this.ioExecutor = ioExecutor;
+		this.wakefulIoExecutor = wakefulIoExecutor;
+		this.maxLatency = maxLatency;
+		this.maxIdleTime = maxIdleTime;
+
+		this.rf = new L2PReaderFactory(callback);
+		this.devicemanager = new DeviceManager();
+	}
+
+	public LoRaAdapter getAdapter() {
+		return devicemanager.getAdapter();
+	}
+
+	public boolean TXenabled() {
+		return true;
+	}
+
+	@Override
+	public TransportId getId() {
+		return ID;
+	}
+
+	@Override
+	public long getMaxLatency() {
+		return maxLatency;
+	}
+
+	@Override
+	public int getMaxIdleTime() {
+		// Unused for simplex transports
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public boolean isLossyAndCheap() {
+		return false;
+	}
+
+	@Override
+	public State getState() {
+		LOG.info("getState(): "+devicemanager.getState());
+		return devicemanager.getState();
+	}
+
+	@Override
+	public void start() throws PluginException {
+		LOG.info("start()");
+		devicemanager.getAdapter().start();
+		callback.pluginStateChanged(getState());
+	}
+
+	@Override
+	public void stop() throws PluginException {
+		LOG.info("stop()");
+		devicemanager.getAdapter().stop();
+	}
+
+	//TODO
+	@Override
+	public int getReasonsDisabled() {
+		return 0;
+	}
+
+	@Override
+	public boolean shouldPoll() {
+		return true;
+	}
+
+	@Override
+	public int getPollingInterval() {
+		LOG.info("getPollingInterval(): "+backoff.getPollingInterval());
+		return backoff.getPollingInterval();
+	}
+
+	//TODO: Switch to Events
+	@Override
+	public void poll(Collection<Pair<TransportProperties, ConnectionHandler>> properties) {
+		//backoff.increment();
+		LOG.info("poll()");
+		if (getState() != ACTIVE || !TXenabled()) return;
+		backoff.increment();
+		for (Pair<TransportProperties, ConnectionHandler> p : properties) {
+			wakefulIoExecutor.execute(() -> {
+				//TODO: Is there anything to send?
+				TransportConnectionWriter w = new L2PWriter(this);
+				if (w != null) {
+					backoff.reset();
+					p.getSecond().handleWriter(w);
+				}
+			});
+		}
+	}
+
+	@Override
+	public TransportConnectionReader createReader(TransportProperties p) {
+		//TODO: Where is that called?
+		LOG.info("createReader()");
+		//callback.handleConnection(conn); used
+		return null;
+		/*try {
+			return new TransportInputStreamReader(openInputStream(p));
+		} catch (IOException e) {
+			logException(LOG, WARNING, e);
+			return null;
+		}*/
+	}
+
+	@Override
+	public TransportConnectionWriter createWriter(TransportProperties p) {
+		//TODO: Where is that called?
+		LOG.info("createWriter()");
+		return new L2PWriter(this);
+	}
+}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/AbstractLoRaPluginFactory.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/AbstractLoRaPluginFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..d78e6234a22cb9180d2ee549092d5afa5e81ff29
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/AbstractLoRaPluginFactory.java
@@ -0,0 +1,73 @@
+package org.briarproject.bramble.plugin.lora;
+
+import org.briarproject.bramble.api.battery.BatteryManager;
+import org.briarproject.bramble.api.crypto.CryptoComponent;
+import org.briarproject.bramble.api.event.EventBus;
+import org.briarproject.bramble.api.lifecycle.IoExecutor;
+import org.briarproject.bramble.api.network.NetworkManager;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.api.plugin.Backoff;
+import org.briarproject.bramble.api.plugin.BackoffFactory;
+import org.briarproject.bramble.api.plugin.PluginCallback;
+import org.briarproject.bramble.api.plugin.TransportId;
+import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
+import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
+import org.briarproject.bramble.api.plugin.LoRaConstants;
+import org.briarproject.bramble.api.system.Clock;
+import org.briarproject.bramble.api.system.LocationUtils;
+import org.briarproject.bramble.api.system.ResourceProvider;
+import org.briarproject.bramble.api.system.WakefulIoExecutor;
+
+import java.io.File;
+import java.util.concurrent.Executor;
+import java.util.logging.Logger;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import javax.net.SocketFactory;
+
+import static java.util.logging.Level.INFO;
+import static java.util.logging.Logger.getLogger;
+
+@Immutable
+@NotNullByDefault
+abstract class AbstractLoRaPluginFactory implements SimplexPluginFactory {
+
+	protected static final Logger LOG =
+			getLogger(AbstractLoRaPluginFactory.class.getName());
+
+	protected final BackoffFactory backoffFactory;
+	protected final Clock clock;
+	protected final CryptoComponent crypto;
+	protected final Executor ioExecutor, wakefulIoExecutor;
+
+	AbstractLoRaPluginFactory(Executor ioExecutor,
+	Executor wakefulIoExecutor,BackoffFactory backoffFactory, Clock clock, CryptoComponent crypto) {
+		this.backoffFactory = backoffFactory;
+		this.clock = clock;
+		this.crypto = crypto;
+		this.ioExecutor = ioExecutor;
+		this.wakefulIoExecutor = wakefulIoExecutor;
+	}
+
+	abstract AbstractLoRaPlugin createPluginInstance(Backoff backoff, PluginCallback callback);
+
+	@Override
+	public TransportId getId() {
+		return LoRaConstants.ID;
+	}
+
+	@Override
+	public long getMaxLatency() {
+		return LoRaConstants.MAX_LATENCY;
+	}
+
+	@Override
+	public SimplexPlugin createPlugin(PluginCallback callback) {
+
+		Backoff backoff = backoffFactory.createBackoff(LoRaConstants.MIN_POLLING_INTERVAL, LoRaConstants.MAX_POLLING_INTERVAL, LoRaConstants.BACKOFF_BASE);
+		AbstractLoRaPlugin plugin = createPluginInstance(backoff, callback);
+		//eventBus.addListener(plugin);
+		return plugin;
+	}
+}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/DeviceManager.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/DeviceManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..57de347b181b27a5b499cb8923ef08bac01e001c
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/DeviceManager.java
@@ -0,0 +1,22 @@
+package org.briarproject.bramble.plugin.lora;
+
+import org.briarproject.bramble.plugin.lora.LoRaAdapter;
+import org.briarproject.bramble.api.plugin.Plugin;
+import static org.briarproject.bramble.api.plugin.Plugin.State.*;
+
+public class DeviceManager {
+    protected LoRaAdapter activeModule;
+    protected Plugin.State state = ACTIVE; //TODO
+    
+    public void setModule(LoRaAdapter module) {
+        this.activeModule = module;
+    }
+
+    public LoRaAdapter getAdapter() {
+        return activeModule;
+    }
+    
+	public Plugin.State getState() {
+		return state;
+    }
+}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/L2PReader.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/L2PReader.java
new file mode 100644
index 0000000000000000000000000000000000000000..4ccca106d3fde2737879cc44cc5ffcd045dee620
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/L2PReader.java
@@ -0,0 +1,155 @@
+package org.briarproject.bramble.plugin.lora;
+
+import org.briarproject.bramble.plugin.lora.AbstractLoRaPlugin;
+import org.briarproject.bramble.api.plugin.TransportConnectionReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PipedOutputStream;
+import java.io.PipedInputStream;
+import org.briarproject.bramble.plugin.lora.LoRaAdapter;
+import org.briarproject.bramble.plugin.lora.L2PReaderFactory;
+import org.briarproject.bramble.util.ByteUtils;
+import java.util.Arrays;
+import java.lang.System;
+import org.briarproject.bramble.api.Bytes;
+import java.util.logging.Logger;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.lang.InterruptedException;
+import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
+import static org.briarproject.bramble.api.plugin.LoRaConstants.*;
+
+public class L2PReader implements TransportConnectionReader {
+    private L2PReaderFactory f;
+    private L2PInputStream stream = new L2PInputStream(this);
+    private Long nextSequenceNr = 0L;
+
+	private static final Logger LOG =
+			Logger.getLogger(L2PReader.class.getName());
+
+    public L2PReader(L2PReaderFactory factory) {
+        f = factory;
+    }
+
+    public void parse(byte content[], Bytes tag, long sequenceNR) {
+        LOG.info("reader pkg "+sequenceNR+" with tag "+ByteUtils.bytesToHex(tag.getBytes())+": "+ByteUtils.bytesToHex(content));
+
+        try {
+            //lost a Packet
+            if(sequenceNR != nextSequenceNr) {
+                //we don't have a better mechanism :(
+                LOG.info("read sequenceNr " + sequenceNR + " expected "+nextSequenceNr);
+                this.dispose(false, true);
+            }
+
+            if(sequenceNR == 0) {
+                System.arraycopy(tag.getBytes(),0,content,0,TAG_LENGTH);
+                stream.add(content);
+            } else {
+                stream.add(Arrays.copyOfRange(content,TAG_LENGTH,PKG_SIZE));
+            }
+
+            nextSequenceNr++;
+        } catch (IOException e) {
+            // TODO
+        }
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException {
+        return stream;
+    }
+
+    @Override
+    public void dispose(boolean exception, boolean recognised)
+            throws IOException {
+        f.remove(this);
+    }
+
+    //TODO: Syncronize
+    private class L2PInputStream extends InputStream {
+        private L2PReader r;
+        private LinkedBlockingDeque<Bytes> buffer = new LinkedBlockingDeque<>();
+        private int offset = 0;
+        private boolean closed=false;
+
+        public L2PInputStream(L2PReader r) {
+            this.r = r;
+        }
+
+        public void add(byte n[]) {
+            buffer.offerLast(new Bytes(n));
+        }
+
+        //just use the parent wrapper
+        //@Override
+        //public int read(byte[] b) throws IOException
+
+        //also just use parent wrapper
+        //public long skip(long n) throws IOException
+
+        @Override
+        public int read() throws IOException {
+            if(buffer.size() == 0 && closed)
+                return -1;
+            int ret=0;
+            try {
+                Bytes block = buffer.take();
+                ret = block.getBytes()[offset++];
+                if(offset == block.getBytes().length)
+                    offset = 0;
+                    //next Block
+                else
+                    buffer.putFirst(block);
+            } catch(InterruptedException e) {
+            }
+            
+            return ret;
+        }
+
+        @Override
+        public int read(byte[] b, int off, int len) throws IOException {
+            if(buffer.size() == 0 && closed) {
+                return -1;
+            }
+
+            int read=0;
+            try {
+                while(len > 0 && (buffer.size() > 0 || read == 0)) {
+                    Bytes block = buffer.take();
+                    int l = Math.min(len, block.getBytes().length-offset);
+                    System.arraycopy(block.getBytes(), offset, b, off, l);
+                    offset += l;
+                    off += l;
+                    read += l;
+                    len -= l;
+                    if(offset >= block.getBytes().length)
+                        offset = 0;
+                        //discard block from buffer
+                    else
+                        buffer.putFirst(block);
+                }
+            } catch(InterruptedException e) {
+            }
+
+            return read;
+        }
+
+        @Override
+        public int available() throws IOException {
+            return (buffer.size()>0 && !closed)?PKG_SIZE-TAG_LENGTH:0;
+        }
+
+        @Override
+        public void close() throws IOException {
+            closed=true;
+            buffer.offerLast(new Bytes(new byte[1])); //push dummy data (like padding) to the Reader to not lock the reader
+            r.dispose(false,false);
+        }
+
+        //TODO: needed?
+        @Override
+        public boolean markSupported() {
+            return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/L2PReaderFactory.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/L2PReaderFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..2a28d60f2fc172ba41f16fe4a264d40737c657a7
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/L2PReaderFactory.java
@@ -0,0 +1,82 @@
+package org.briarproject.bramble.plugin.lora;
+
+import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
+import org.briarproject.bramble.plugin.lora.AbstractLoRaPlugin;
+import org.briarproject.bramble.api.plugin.TransportConnectionReader;
+import java.io.IOException;
+import java.io.InputStream;
+import org.briarproject.bramble.plugin.lora.LoRaAdapter;
+import org.briarproject.bramble.plugin.lora.L2PReader;
+import org.briarproject.bramble.plugin.lora.L2PTagManager;
+import java.util.Arrays;
+import java.lang.Integer;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Collections;
+import java.util.logging.Logger;
+import org.briarproject.bramble.util.ByteUtils;
+import org.briarproject.bramble.api.Bytes;
+import org.briarproject.bramble.api.Pair;
+import org.briarproject.bramble.api.plugin.PluginCallback;
+import org.briarproject.bramble.api.plugin.LoRaConstants;
+
+//TODO: Make singleton
+public class L2PReaderFactory {
+    //TODO: Do I need syncronisation here?
+    private Map<Bytes, L2PReader> readers = Collections.synchronizedMap(new HashMap<Bytes, L2PReader>());
+
+    private PluginCallback callback;
+	private static final Logger LOG =
+            Logger.getLogger(L2PReaderFactory.class.getName());
+        
+    private final L2PTagManager l2pTags;
+
+    public L2PReaderFactory(PluginCallback callback) {
+        this.callback = callback;
+        l2pTags = L2PTagManager.TODOuseDependencyInjection(LoRaConstants.ID);
+        l2pTags.setOpenReaders(readers);
+    }
+
+    private L2PReader createReader() {
+        L2PReader r = new L2PReader(this);
+        callback.handleReader(r);
+        return r;
+    }
+
+    private L2PReader getReaderByBTPtag(Bytes btpTag) {
+        L2PReader r = readers.get(btpTag);
+        if(r == null) {
+            r = createReader();
+            readers.put(btpTag, r);
+        }
+
+        return r;
+    }
+
+    public void parse(byte[] content) {
+        byte[] l2pTag = Arrays.copyOf(content, TAG_LENGTH);
+        Pair<Bytes,Long> tagData = l2pTags.get(new Bytes(l2pTag));
+        if(tagData == null) {
+            //this packet isn't recognised => it probably is no briar packet => ignore
+            LOG.info("dropping LoRa pkg "+ByteUtils.bytesToHex(content));
+            LOG.info("Tags:"+l2pTags.toString());
+            return;
+        }
+        getReaderByBTPtag(tagData.getFirst()).parse(content, tagData.getFirst(), tagData.getSecond().intValue());
+
+        //TODO: Drop Tag if successfull!
+
+        //generate next Tags
+        l2pTags.addNfromBTPtag(tagData.getFirst(), tagData.getSecond()+1, L2PTagManager.CALCULATE_OPEN_CONN);
+    }
+
+    public void remove(L2PReader r) {
+        for(Entry<Bytes,L2PReader> i: readers.entrySet())
+            if(r.equals(i.getValue())) {
+                Bytes BTPtag = i.getKey();
+                readers.remove(BTPtag);
+                l2pTags.removeAllFromBTPtag(BTPtag);
+                return;
+            }
+    }
+}
\ No newline at end of file
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/L2PTagManager.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/L2PTagManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..f904152e6e472fef415b7e8fa5aa8ab7423fc6be
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/L2PTagManager.java
@@ -0,0 +1,137 @@
+package org.briarproject.bramble.plugin.lora;
+
+import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
+import org.briarproject.bramble.plugin.lora.AbstractLoRaPlugin;
+import org.briarproject.bramble.api.plugin.TransportConnectionReader;
+import java.io.IOException;
+import java.io.InputStream;
+import org.briarproject.bramble.plugin.lora.LoRaAdapter;
+import org.briarproject.bramble.plugin.lora.L2PReader;
+import java.util.Arrays;
+import java.lang.Long;
+import java.util.HashMap;
+import java.util.Map;
+import java.lang.System;
+import java.util.Collections;
+import java.util.logging.Logger;
+import org.briarproject.bramble.util.ByteUtils;
+import org.briarproject.bramble.api.Bytes;
+import org.briarproject.bramble.api.Pair;
+import org.briarproject.bramble.api.plugin.PluginCallback;
+import org.briarproject.bramble.api.plugin.TransportId;
+import org.briarproject.bramble.api.plugin.LoRaConstants;
+import org.briarproject.bramble.plugin.lora.L2PReader;
+import org.briarproject.bramble.api.crypto.TransportCrypto;
+import java.util.Map.Entry;
+import java.util.Iterator;
+import java.util.Collection;
+
+import org.bouncycastle.crypto.CryptoException;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.Blake2bDigest;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.logging.Level.WARNING;
+import static java.util.logging.Logger.getLogger;
+import static org.briarproject.bramble.api.nullsafety.NullSafety.requireExactlyOneNull;
+import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
+import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
+import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
+import static org.briarproject.bramble.util.LogUtils.logException;
+import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
+import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
+import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
+import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
+import static org.briarproject.bramble.util.ByteUtils.writeUint16;
+import static org.briarproject.bramble.util.ByteUtils.writeUint64;
+import java.util.logging.Logger;
+
+
+
+public class L2PTagManager {
+
+	private static final Logger LOG =
+            Logger.getLogger(L2PTagManager.class.getName());
+    
+
+    //TODO: For better performance, we should choose a data-structure that is indexed by B2PTag as well as L2PTag
+    private final Map<Bytes, Pair<Bytes,Long>> l2pTags = Collections.synchronizedMap(new HashMap<Bytes, Pair<Bytes,Long>>());
+    private Map<Bytes, L2PReader> openReaders;
+//    private TransportCrypto crypto; //move genL2PTag to TransportCrypto
+
+    public String toString() {
+        return l2pTags.toString();
+    }
+
+    //TODO: Evaluate what makes sense using a simple or sophisticated protocol
+    public static final int CALCULATE_STD = 4;
+    public static final int CALCULATE_OPEN_CONN = 16;
+
+    public L2PTagManager() {
+        //Init crypto with TransportCryptoImpl including dependency injection
+    }
+
+    public void addNfromBTPtag(final Bytes inTag, long start, int n) {
+        for(long i=start; i<start+n; i++) {
+            l2pTags.put(genL2PTag(inTag, i), new Pair<Bytes,Long>(inTag, i));
+        }
+    }
+
+    //Bytes: BTP-Tag, Integer: sequenceNR
+    public Pair<Bytes,Long> get(Bytes L2PTag) {
+        return l2pTags.get(L2PTag);
+    }
+
+    public void remove(Bytes l2pTag) {
+        l2pTags.remove(l2pTag);
+    }
+
+    public void removeAllFromBTPtag(Bytes inTag) {
+        if(openReaders.get(inTag) != null)
+            return;
+
+        //TODO: More elegant option to remove Elements using lambda?
+        Iterator<Pair<Bytes, Long>> i = l2pTags.values().iterator();
+        while(i.hasNext())
+            if(inTag.equals(i.next().getSecond()))
+                i.remove();
+    }
+
+    public void clear() {
+        l2pTags.clear();
+    }
+
+    public void setOpenReaders(Map<Bytes, L2PReader> rs) {
+        this.openReaders = rs;
+    }
+
+    public static Bytes genL2PTag(final Bytes inTag, long i) {
+        //I don't understand the 32 Bytes, I just copied it from TransportCryptoImpl ...
+        Digest prf = new Blake2bDigest(inTag.getBytes(), 32, null, null);
+		// The output of the PRF must be long enough to use as a tag
+		int macLength = prf.getDigestSize();
+		if (macLength < TAG_LENGTH) throw new IllegalStateException();
+
+//		byte[] protocolVersionBytes = new byte[INT_16_BYTES];
+//		writeUint16(protocolVersion, protocolVersionBytes, 0);
+//		prf.update(protocolVersionBytes, 0, protocolVersionBytes.length);
+
+        //TODO: could break a stream after 2^62 frames - would be Zettabytes of data
+		byte[] sequenceNrBuffer = new byte[INT_64_BYTES];
+		writeUint64(i, sequenceNrBuffer, 0);
+		prf.update(sequenceNrBuffer, 0, sequenceNrBuffer.length);
+		byte[] mac = new byte[macLength];
+        prf.doFinal(mac, 0);
+        
+		// The output is the first TAG_LENGTH bytes of the MAC
+        byte[] tag = new byte[TAG_LENGTH];
+        System.arraycopy(mac, 0, tag, 0, TAG_LENGTH);
+        return new Bytes(tag);
+    }
+
+    //TODO: Move to Dependency Injection
+    private static final L2PTagManager m = new L2PTagManager();
+    public static L2PTagManager TODOuseDependencyInjection(TransportId t) {
+        return m;
+    }
+}
\ No newline at end of file
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/L2PWriter.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/L2PWriter.java
new file mode 100644
index 0000000000000000000000000000000000000000..e86ef37b98df838c9a0f3ea5b68e5149c5e9d568
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/L2PWriter.java
@@ -0,0 +1,124 @@
+package org.briarproject.bramble.plugin.lora;
+
+import org.briarproject.bramble.plugin.lora.AbstractLoRaPlugin;
+import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import org.briarproject.bramble.plugin.lora.LoRaAdapter;
+import java.util.Arrays;
+import java.lang.System;
+import java.util.logging.Logger;
+import org.briarproject.bramble.util.ByteUtils;
+import org.briarproject.bramble.plugin.lora.L2PTagManager;
+import org.briarproject.bramble.api.Bytes;
+
+import static org.briarproject.bramble.api.plugin.LoRaConstants.*;
+import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
+
+public class L2PWriter implements TransportConnectionWriter {
+
+	private static final Logger LOG =
+			Logger.getLogger(L2PWriter.class.getName());
+
+    private AbstractLoRaPlugin plugin;
+
+    L2PWriter(AbstractLoRaPlugin plugin) {
+		LOG.info("L2PWriter::Constructor");
+        this.plugin = plugin;
+    }
+
+    @Override
+    public long getMaxLatency() {
+        return plugin.getMaxLatency();
+    }
+
+    @Override
+    public int getMaxIdleTime() {
+        return plugin.getMaxIdleTime();
+    }
+
+    @Override
+    public boolean isLossyAndCheap() {
+        return false;
+    }
+
+    @Override
+    public OutputStream getOutputStream() throws IOException {
+        return new L2OutputStream(plugin.getAdapter());
+    }
+
+    @Override
+    public void dispose(boolean exception) throws IOException {
+    }
+
+    private class L2OutputStream extends OutputStream {
+        private LoRaAdapter adapter;
+        private byte[] buffer;
+        private byte[] btpTag = new byte[TAG_LENGTH];
+        private short readTagLength=0;
+        private long sequenceNr=0;
+
+        L2OutputStream(LoRaAdapter adapter) {
+            this.adapter = adapter;
+            buffer = new byte[0];
+        }
+    
+        @Override
+        public void close() throws IOException {
+            flush();
+            LOG.info("close()");
+        }
+    
+        @Override
+        public void write(int b) throws IOException {
+            write(new byte[]{(byte)b}, 0, 1);
+        }
+    
+        @Override
+        public void write(byte[] b) throws IOException {
+            write(b, 0, b.length);
+            //flush();
+        }
+
+        @Override
+        public void write(byte[] b, int off, int len) throws IOException {
+            LOG.info("WRITE ("+len+" Bytes)");
+            LOG.info(ByteUtils.bytesToHex(b));
+
+            //Read BTP-Tag first (if not done) and write it in another Array
+            while(readTagLength < TAG_LENGTH && len > 0) {
+                btpTag[readTagLength++] = b[off++];
+                len--;
+            }
+
+            if(len <= 0) //for speed
+                return;
+
+            if(off != 0) {
+                byte tmp[] = new byte[len];
+                System.arraycopy(b,off,tmp,0,len);
+                b = tmp;
+            } else if(b.length != len)
+                b = Arrays.copyOf(b,len);
+
+            buffer = ByteUtils.concat(buffer, b);
+            while(buffer.length >= PAYLOAD_SIZE) {
+                flush();
+            }
+        }
+
+        @Override
+        public void flush() {
+            if(buffer.length != 0) {
+                int len = Math.min(PAYLOAD_SIZE, buffer.length);
+
+                byte[] pkg = new byte[len+TAG_LENGTH];
+                System.arraycopy(L2PTagManager.genL2PTag(new Bytes(btpTag), sequenceNr++).getBytes(), 0, pkg, 0, TAG_LENGTH); //Write L2PTag
+                System.arraycopy(buffer, 0, pkg, TAG_LENGTH, len);
+                adapter.send(pkg);
+                
+                buffer = Arrays.copyOfRange(buffer, len, buffer.length);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/LoRaAdapter.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/LoRaAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..29e48fcb832972d581d1d4eaa77ef16775e7c4fb
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/lora/LoRaAdapter.java
@@ -0,0 +1,9 @@
+package org.briarproject.bramble.plugin.lora;
+
+import java.util.List;
+
+public interface LoRaAdapter {
+	public void send(byte[] payload);
+	public void start();
+	public void stop();
+}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java
index 1b1764137e2f5c6825f0398bcc360afed77bc051..9622edd58ab0186bbe37403c4c7e2126aee6b0c0 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java
@@ -85,6 +85,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
 		try {
 			db.transaction(false, txn -> {
 				for (PluginFactory<?> f : pluginConfig.getSimplexFactories()) {
+					LOG.info("addTransport(...,"+f.getId()+")");
 					addTransport(txn, f);
 				}
 				for (PluginFactory<?> f : pluginConfig.getDuplexFactories()) {
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java
index 788d51d3cac440fb4c9bd537008ac3661bee057a..dd41ea4988be0b6b225f4870080bc355701bd765 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java
@@ -19,6 +19,9 @@ import org.briarproject.bramble.api.transport.TransportKeySet;
 import org.briarproject.bramble.api.transport.TransportKeys;
 import org.briarproject.bramble.transport.ReorderingWindow.Change;
 import org.briarproject.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.util.ByteUtils;
+import org.briarproject.bramble.plugin.lora.L2PTagManager;
+import org.briarproject.bramble.api.plugin.LoRaConstants;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -72,6 +75,9 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 	private final Map<PendingContactId, MutableTransportKeySet>
 			pendingContactOutContexts = new HashMap<>();
 
+	@Nullable
+	private final L2PTagManager l2ptm;
+
 	TransportKeyManagerImpl(DatabaseComponent db,
 			TransportCrypto transportCrypto,
 			Executor dbExecutor,
@@ -86,10 +92,17 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 		this.clock = clock;
 		this.transportId = transportId;
 		timePeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE;
+
+		if(LoRaConstants.ID.equals(transportId))
+				l2ptm = L2PTagManager.TODOuseDependencyInjection(LoRaConstants.ID);
+		else
+				l2ptm = null;
 	}
 
 	@Override
 	public void start(Transaction txn) throws DbException {
+		LOG.info("TransportKeyManager::start()");
+
 		if (used.getAndSet(true)) throw new IllegalStateException();
 		long now = clock.currentTimeMillis();
 		lock.lock();
@@ -97,13 +110,18 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 			// Load the transport keys from the DB
 			Collection<TransportKeySet> loaded =
 					db.getTransportKeys(txn, transportId);
+			LOG.info("loaded="+loaded);
 			// Update the keys to the current time period
 			UpdateResult updateResult = updateKeys(loaded, now);
+			LOG.info("updated="+updateResult.updated);
 			// Initialise mutable state for all contacts
 			addKeys(updateResult.current);
 			// Write any updated keys back to the DB
 			if (!updateResult.updated.isEmpty())
 				db.updateTransportKeys(txn, updateResult.updated);
+		} catch(Exception e) {
+			LOG.info(e.toString());
+			throw e;
 		} finally {
 			lock.unlock();
 		}
@@ -115,10 +133,12 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 			long now) {
 		UpdateResult updateResult = new UpdateResult();
 		long timePeriod = now / timePeriodLength;
+		LOG.info("current timeperiod:"+timePeriod);
 		for (TransportKeySet ks : keys) {
 			TransportKeys k = ks.getKeys();
 			TransportKeys k1 = transportCrypto.updateTransportKeys(k,
 					timePeriod);
+			LOG.info("k1:"+k1);
 			if (k1.getTimePeriod() > k.getTimePeriod()) {
 				TransportKeySet ks1 = new TransportKeySet(ks.getKeySetId(),
 						ks.getContactId(), ks.getPendingContactId(), k1);
@@ -168,7 +188,13 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 			byte[] tag = new byte[TAG_LENGTH];
 			transportCrypto.encodeTag(tag, inKeys.getTagKey(), PROTOCOL_VERSION,
 					streamNumber);
-			inContexts.put(new Bytes(tag), tagCtx);
+			Bytes tagObj = new Bytes(tag);
+			inContexts.put(tagObj, tagCtx);
+			LOG.info("New Tag for "+contactId+" - "+streamNumber);
+
+
+			if(l2ptm != null)
+				l2ptm.addNfromBTPtag(tagObj, 0, L2PTagManager.CALCULATE_STD);
 		}
 	}
 
@@ -292,6 +318,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 		}
 	}
 
+	//TODO: remove from L2PTagManager
 	@Override
 	public void removeContact(ContactId c) {
 		lock.lock();
@@ -299,16 +326,19 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 			// Remove mutable state for the contact
 			Iterator<TagContext> it = inContexts.values().iterator();
 			while (it.hasNext())
-				if (c.equals(it.next().contactId)) it.remove();
+				if (c.equals(it.next().contactId))
+					it.remove();
 			contactOutContexts.remove(c);
 			Iterator<MutableTransportKeySet> it1 = keys.values().iterator();
 			while (it1.hasNext())
-				if (c.equals(it1.next().getContactId())) it1.remove();
+				if (c.equals(it1.next().getContactId()))
+					it1.remove();
 		} finally {
 			lock.unlock();
 		}
 	}
 
+	//TODO: remove from L2PTagManager
 	@Override
 	public void removePendingContact(PendingContactId p) {
 		lock.lock();
@@ -415,8 +445,10 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 	@GuardedBy("lock")
 	@Nullable
 	private StreamContext streamContextFromTag(byte[] tag) {
+		LOG.info("try to recognize tag "+ByteUtils.bytesToHex(tag)+" possibilities:"+inContexts.toString());
 		// Look up the incoming keys for the tag
 		TagContext tagCtx = inContexts.get(new Bytes(tag));
+		LOG.info("TagContext: "+(tagCtx!=null?tagCtx.toString():"null"));
 		if (tagCtx == null) return null;
 		MutableIncomingKeys inKeys = tagCtx.inKeys;
 		// Create a stream context
@@ -430,6 +462,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 	public void markTagAsRecognised(Transaction txn, byte[] tag)
 			throws DbException {
 		TagContext tagCtx = inContexts.remove(new Bytes(tag));
+		//TODO: do nothing for L2PTagManager, or am I wrong?
 		if (tagCtx == null) return;
 		MutableIncomingKeys inKeys = tagCtx.inKeys;
 		// Update the reordering window
@@ -443,7 +476,13 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 			TagContext tagCtx1 = new TagContext(tagCtx.keySetId,
 					tagCtx.contactId, tagCtx.pendingContactId, inKeys,
 					streamNumber, tagCtx.handshakeMode);
-			inContexts.put(new Bytes(addTag), tagCtx1);
+			Bytes addTagObj = new Bytes(addTag);
+			inContexts.put(addTagObj, tagCtx1);
+			LOG.info("New Tag for "+tagCtx.contactId+" - "+streamNumber);
+
+
+			if(l2ptm != null)
+				l2ptm.addNfromBTPtag(addTagObj, 0, L2PTagManager.CALCULATE_STD);
 		}
 		// Remove tags for any stream numbers removed from the window
 		for (long streamNumber : change.getRemoved()) {
@@ -451,7 +490,11 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 			byte[] removeTag = new byte[TAG_LENGTH];
 			transportCrypto.encodeTag(removeTag, inKeys.getTagKey(),
 					PROTOCOL_VERSION, streamNumber);
-			inContexts.remove(new Bytes(removeTag));
+			Bytes removeTagObj = new Bytes(removeTag);
+			inContexts.remove(removeTagObj);
+
+			if(l2ptm != null)
+				l2ptm.removeAllFromBTPtag(removeTagObj);
 		}
 		// Write the window back to the DB
 		db.setReorderingWindow(txn, tagCtx.keySetId, transportId,
@@ -488,6 +531,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 			contactOutContexts.clear();
 			pendingContactOutContexts.clear();
 			keys.clear();
+			if(l2ptm != null)
+				l2ptm.clear();
 			addKeys(updateResult.current);
 			// Write any updated keys back to the DB
 			if (!updateResult.updated.isEmpty())
@@ -522,6 +567,11 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 			this.streamNumber = streamNumber;
 			this.handshakeMode = handshakeMode;
 		}
+
+		public String toString() {
+			return "KeySetId: "+keySetId.getInt()+" streamNumber: "+streamNumber;
+			//+" ContactId: "+contactId.getInt()+" handshakeMode: "+handshakeMode+" PendingContactId: "+pendingContactId
+		}
 	}
 
 	private static class UpdateResult {
diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/lora/UnixLoRaPlugin.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/lora/UnixLoRaPlugin.java
new file mode 100644
index 0000000000000000000000000000000000000000..0ec2e705ab66deb196242b1b89dd80ca50099d38
--- /dev/null
+++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/lora/UnixLoRaPlugin.java
@@ -0,0 +1,35 @@
+package org.briarproject.bramble.plugin.lora;
+
+import com.sun.jna.Library;
+import com.sun.jna.Native;
+
+import org.briarproject.bramble.api.battery.BatteryManager;
+import org.briarproject.bramble.api.network.NetworkManager;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.api.plugin.Backoff;
+import org.briarproject.bramble.api.plugin.PluginCallback;
+import org.briarproject.bramble.api.system.Clock;
+import org.briarproject.bramble.api.system.LocationUtils;
+import org.briarproject.bramble.api.system.ResourceProvider;
+import org.briarproject.bramble.plugin.lora.adapter.*;
+import org.briarproject.bramble.plugin.lora.DeviceManager;
+
+import java.io.File;
+import java.util.concurrent.Executor;
+
+import javax.net.SocketFactory;
+
+@NotNullByDefault
+class UnixLoRaPlugin extends AbstractLoRaPlugin {
+
+	UnixLoRaPlugin(
+		Executor ioExecutor,
+		Executor wakefulIoExecutor,Clock clock,
+			Backoff backoff,
+			PluginCallback callback,
+			long maxLatency,
+			int maxIdleTime) {
+		super(ioExecutor, wakefulIoExecutor,clock, backoff, callback, maxLatency, maxIdleTime);
+		devicemanager.setModule(new Pinedio(rf));
+	}
+}
diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/lora/UnixLoRaPluginFactory.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/lora/UnixLoRaPluginFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..7721580fd10024c3d339113d254ec24cba4a4ddd
--- /dev/null
+++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/lora/UnixLoRaPluginFactory.java
@@ -0,0 +1,43 @@
+package org.briarproject.bramble.plugin.lora;
+
+import org.briarproject.bramble.api.battery.BatteryManager;
+import org.briarproject.bramble.api.crypto.CryptoComponent;
+import org.briarproject.bramble.api.event.EventBus;
+import org.briarproject.bramble.api.lifecycle.IoExecutor;
+import org.briarproject.bramble.api.network.NetworkManager;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.api.plugin.Backoff;
+import org.briarproject.bramble.api.plugin.BackoffFactory;
+import org.briarproject.bramble.api.plugin.PluginCallback;
+import org.briarproject.bramble.api.system.Clock;
+import org.briarproject.bramble.api.system.LocationUtils;
+import org.briarproject.bramble.api.system.ResourceProvider;
+import org.briarproject.bramble.api.system.WakefulIoExecutor;
+import org.briarproject.bramble.plugin.lora.AbstractLoRaPluginFactory;
+import org.briarproject.bramble.plugin.lora.AbstractLoRaPlugin;
+import org.briarproject.bramble.api.plugin.LoRaConstants;
+
+import java.io.File;
+import java.util.concurrent.Executor;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import javax.inject.Inject;
+import javax.net.SocketFactory;
+
+import static java.util.logging.Level.INFO;
+
+@Immutable
+@NotNullByDefault
+public class UnixLoRaPluginFactory extends AbstractLoRaPluginFactory {
+
+	@Inject
+	UnixLoRaPluginFactory(@IoExecutor Executor ioExecutor, @WakefulIoExecutor Executor wakefulIoExecutor,BackoffFactory backoffFactory, Clock clock, CryptoComponent crypto) {
+		super(ioExecutor, wakefulIoExecutor, backoffFactory, clock, crypto);
+	}
+
+	@Override
+	AbstractLoRaPlugin createPluginInstance(Backoff backoff, PluginCallback callback) {
+		return new UnixLoRaPlugin(ioExecutor, wakefulIoExecutor, clock, backoff, callback, LoRaConstants.MAX_LATENCY, LoRaConstants.MAX_IDLE_TIME);
+	}
+}
diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/lora/adapter/Pinedio.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/lora/adapter/Pinedio.java
new file mode 100644
index 0000000000000000000000000000000000000000..c15f105f663ee1699f4a7af302fd71e40d1092e5
--- /dev/null
+++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/lora/adapter/Pinedio.java
@@ -0,0 +1,87 @@
+package org.briarproject.bramble.plugin.lora.adapter;
+
+import org.briarproject.bramble.plugin.lora.LoRaAdapter;
+import org.briarproject.bramble.plugin.lora.L2PReaderFactory;
+
+import java.lang.System;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+import com.sun.jna.Library;
+import com.sun.jna.Native;
+import com.sun.jna.Platform;
+import com.sun.jna.Callback;
+import com.sun.jna.Pointer;
+import org.briarproject.bramble.util.ByteUtils;
+
+import javax.inject.Inject;
+import java.util.logging.Logger;
+
+public class Pinedio implements LoRaAdapter {
+	private final SecureRandom secureRandom;
+	private L2PReaderFactory rf;
+
+	private static final Logger LOG =
+			Logger.getLogger(Pinedio.class.getName());
+
+
+    private CLibrary.appRxCallback_ RxCallback = new CLibrary.appRxCallback_() {
+		public void invoke(Pointer payload, byte RssiPkt, byte SnrPkt, byte SignalRssiPkt) {
+            byte[] b = payload.getByteArray(0,255);
+			LOG.info("Rx "+ByteUtils.bytesToHex(b));
+			
+			rf.parse(b);
+        }
+    };
+
+    private interface CLibrary extends Library {
+		CLibrary INSTANCE = (CLibrary)Native.loadLibrary("pinedio-lora-driver", CLibrary.class);
+
+        interface appRxCallback_ extends Callback {
+            void invoke(Pointer payload, byte RssiPkt, byte SnrPkt, byte SignalRssiPkt);
+        }
+
+        void PinedioLoraRadioInitialize(appRxCallback_ RxCallback);
+        void PinedioLoraRadioSend(byte payload[]); //make sure we have exactly 255 elements!!
+        void PinedioLoraRadioStart();
+        void PinedioLoraRadioStop();
+	}
+	
+//	@Inject
+	public Pinedio(L2PReaderFactory rf) {
+		this.rf = rf;
+
+		this.secureRandom = new SecureRandom(); //TODO: Dependency Inection
+		System.setProperty("jna.debug_load", "true");
+		System.setProperty("jna.library.path", "/root/Bakk2/briar/");
+		
+		CLibrary.INSTANCE.PinedioLoraRadioInitialize(RxCallback);
+		//TODO: Stop
+	}
+
+	public void start() {
+		CLibrary.INSTANCE.PinedioLoraRadioStart();
+	}
+
+	public void stop() {
+		CLibrary.INSTANCE.PinedioLoraRadioStop();
+	}
+
+	public void send(byte payload[]) {
+		//padding - the end of Stream is already known at a higher level so we can just add random chars
+		int length = payload.length;
+		if(length < 255) {
+			byte[] padding = new byte[255-length];
+			secureRandom.nextBytes(padding);
+
+			payload = Arrays.copyOf(payload, 255);
+			System.arraycopy(padding, 0, payload, length, 255-length);
+		}
+
+		LOG.info("Tx " + ByteUtils.bytesToHex(payload));
+
+		CLibrary.INSTANCE.PinedioLoraRadioSend(payload);
+	}
+
+	//TODO: Destructor
+}
\ No newline at end of file
diff --git a/briar-headless/src/main/java/org/briarproject/briar/headless/HeadlessModule.kt b/briar-headless/src/main/java/org/briarproject/briar/headless/HeadlessModule.kt
index d15db40dd92716270eacb8dab8de80e33a859ca7..4a72b2f0290afffac891c3e97571c2cb2a1dfde8 100644
--- a/briar-headless/src/main/java/org/briarproject/briar/headless/HeadlessModule.kt
+++ b/briar-headless/src/main/java/org/briarproject/briar/headless/HeadlessModule.kt
@@ -20,6 +20,7 @@ import org.briarproject.bramble.battery.DefaultBatteryManagerModule
 import org.briarproject.bramble.event.DefaultEventExecutorModule
 import org.briarproject.bramble.plugin.tor.UnixTorPluginFactory
 import org.briarproject.bramble.plugin.tor.WindowsTorPluginFactory
+import org.briarproject.bramble.plugin.lora.UnixLoRaPluginFactory
 import org.briarproject.bramble.system.ClockModule
 import org.briarproject.bramble.system.DefaultTaskSchedulerModule
 import org.briarproject.bramble.system.DefaultThreadFactoryModule
@@ -92,16 +93,21 @@ internal class HeadlessModule(private val appDir: File) {
     @Singleton
     internal fun providePluginConfig(
         unixTor: UnixTorPluginFactory,
-        winTor: WindowsTorPluginFactory
+        winTor: WindowsTorPluginFactory,
+        unixlora: UnixLoRaPluginFactory
     ): PluginConfig {
         val duplex: List<DuplexPluginFactory> = when {
             isLinux() || isMac() -> listOf(unixTor)
             isWindows() -> listOf(winTor)
             else -> emptyList()
         }
+        var simplex: List<SimplexPluginFactory> = when {
+            isLinux() || isMac() -> listOf(unixLoRa)
+            else -> emptyList()
+        }
         return object : PluginConfig {
             override fun getDuplexFactories(): Collection<DuplexPluginFactory> = duplex
-            override fun getSimplexFactories(): Collection<SimplexPluginFactory> = emptyList()
+            override fun getSimplexFactories(): Collection<SimplexPluginFactory> = simplex
             override fun shouldPoll(): Boolean = true
             override fun getTransportPreferences(): Map<TransportId, List<TransportId>> = emptyMap()
         }
diff --git a/briar-headless/src/main/java/org/briarproject/briar/headless/blogs/BlogControllerImpl.kt b/briar-headless/src/main/java/org/briarproject/briar/headless/blogs/BlogControllerImpl.kt
index 2df0d6dccfe32013212b32531ffc52daaa91d829..6c0b416539e00dfd4f1b1f6777ec41c1e5732790 100644
--- a/briar-headless/src/main/java/org/briarproject/briar/headless/blogs/BlogControllerImpl.kt
+++ b/briar-headless/src/main/java/org/briarproject/briar/headless/blogs/BlogControllerImpl.kt
@@ -49,7 +49,7 @@ constructor(
         val blog = blogManager.getPersonalBlog(author)
         val now = clock.currentTimeMillis()
         val post = blogPostFactory.createBlogPost(blog.id, now, null, author, text)
-        val header = db.transactionWithResult<BlogPostHeader, DbException>(true) { txn ->
+        val header = db.transactionWithResult<BlogPostHeader, DbException>(false) { txn ->
             blogManager.addLocalPost(txn, post)
             return@transactionWithResult blogManager.getPostHeader(txn, blog.id, post.message.id)
         }