blob: 8b78329967573c743fbcdf9b5cd84bf125620933 [file] [log] [blame]
/*
* jndn-mock
* Copyright (c) 2013-2015 Regents of the University of California.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU Lesser General Public License,
* version 3, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
* more details.
*/
package com.intel.jndn.mock;
import net.named_data.jndn.*;
import net.named_data.jndn.encoding.ElementListener;
import net.named_data.jndn.encoding.ElementReader;
import net.named_data.jndn.encoding.EncodingException;
import net.named_data.jndn.encoding.TlvWireFormat;
import net.named_data.jndn.encoding.tlv.Tlv;
import net.named_data.jndn.encoding.tlv.TlvDecoder;
import net.named_data.jndn.encoding.tlv.TlvEncoder;
import net.named_data.jndn.security.KeyChain;
import net.named_data.jndn.security.SecurityException;
import net.named_data.jndn.transport.Transport;
import net.named_data.jndn.util.Blob;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;
/** A client-side face for unit testing
*/
public class MockFace extends Face
{
public interface SignalOnSendInterest {
void emit(Interest interest) throws EncodingException, SecurityException;
}
public interface SignalOnSendData {
void emit(Data data);
}
/**
* Options for MockFace
*/
public static class Options
{
/** If true, packets sent out of MockFace will be appended to a container
*/
boolean enablePacketLogging = false;
/**
* If true, prefix registration command will be automatically replied with a successful response
*/
boolean enableRegistrationReply = false;
}
final public static Options DEFAULT_OPTIONS = new Options(){{ enablePacketLogging=true; enableRegistrationReply=true; }};
/**
* Create MockFace that logs packets in sentInterests and sentData and emulates NFD prefix registration
*/
public MockFace() throws SecurityException {
this(DEFAULT_OPTIONS);
}
/**
* Create MockFace with the specified options
* <p>
* To create Face that does not log packets:
* <pre>
* new MockFace(new Options());
* // use onSendInterest.add(handler) and onSendData.add(handler)
* // to add custom logic when Interest or Data packet are sent
* // from the upper level (to transport)
* </pre>
*
* To create Face that just logs packets in sentInterests and sentData:
* <pre>
* new MockFace(new Options(){{ enablePacketLogging=true; }});
* </pre>
*/
public MockFace(Options options) throws SecurityException {
super(new MockFaceTransport(), null);
m_transport = (MockFaceTransport)node_.getTransport();
m_keychain = MockKeyChain.configure(new Name("/mock/key"));
setCommandSigningInfo(m_keychain, m_keychain.getDefaultCertificateName());
m_transport.onSendBlock = new MockFaceTransport.OnSendBlockSignal() {
@Override
public void emit(ByteBuffer buffer) throws EncodingException, SecurityException {
// @todo Implement NDNLP processing
if (buffer.get(0) == Tlv.Interest || buffer.get(0) == Tlv.Data) {
TlvDecoder decoder = new TlvDecoder(buffer);
if (decoder.peekType(Tlv.Interest, buffer.remaining())) {
Interest interest = new Interest();
interest.wireDecode(buffer, TlvWireFormat.get());
for (SignalOnSendInterest signal : onSendInterest) {
signal.emit(interest);
}
} else if (decoder.peekType(Tlv.Data, buffer.remaining())) {
Data data = new Data();
data.wireDecode(buffer, TlvWireFormat.get());
for (SignalOnSendData signal : onSendData) {
signal.emit(data);
}
}
}
else {
logger.info("Received an unknown packet");
}
}
};
if (options.enablePacketLogging) {
onSendInterest.add(new SignalOnSendInterest() {
@Override
public void emit(Interest interest) {
sentInterests.add(interest);
}
});
onSendData.add(new SignalOnSendData() {
@Override
public void emit(Data data) {
sentData.add(data);
}
});
}
if (options.enableRegistrationReply) {
onSendInterest.add(new SignalOnSendInterest() {
@Override
public void emit(Interest interest) throws EncodingException, SecurityException {
final Name localhostRegistration = new Name("/localhost/nfd/rib");
if (!interest.getName().getPrefix(3).equals(localhostRegistration)) {
return;
}
ControlParameters params = new ControlParameters();
params.wireDecode(interest.getName().get(-5).getValue());
params.setFaceId(1);
params.setOrigin(0);
if (interest.getName().get(3).toString().equals("register")) {
params.setCost(0);
}
// TODO: replace with jNDN ControlResponse encoding when available
// http://redmine.named-data.net/issues/3455
TlvEncoder encoder = new TlvEncoder(256);
int saveLength = encoder.getLength();
encoder.writeBuffer(params.wireEncode().buf());
encoder.writeBlobTlv(Tlv.NfdCommand_StatusText, new Blob("OK").buf());
encoder.writeNonNegativeIntegerTlv(Tlv.NfdCommand_StatusCode, 200);
encoder.writeTypeAndLength(Tlv.NfdCommand_ControlResponse, encoder.getLength() - saveLength);
Data data = new Data();
data.setName(interest.getName());
data.setContent(new Blob(encoder.getOutput(), false));
m_keychain.sign(data);
receive(data);
}
});
}
}
/**
* Mock reception of the Interest packet on the Face (from transport)
*/
public void receive(Interest interest) throws EncodingException {
m_transport.receive(interest.wireEncode().buf());
}
/**
* Mock reception of the Data packet on the Face (from transport)
*/
public void receive(Data data) throws EncodingException {
m_transport.receive(data.wireEncode().buf());
}
public Transport getTransport() {
return m_transport;
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
/**
* Internal transport for MockFace
*/
private static class MockFaceTransport extends Transport {
public interface OnSendBlockSignal {
void emit(ByteBuffer buffer) throws EncodingException, SecurityException;
}
public void
receive(ByteBuffer block) throws EncodingException {
synchronized (m_recvBuffer) {
m_recvBuffer.add(block.duplicate());
}
}
// from Transport
@Override
public boolean isLocal(ConnectionInfo connectionInfo) {
return true;
}
@Override
public boolean isAsync() {
return false;
}
@Override
public void connect(Transport.ConnectionInfo connectionInfo,
ElementListener elementListener, Runnable onConnected) {
logger.fine("Connecting...");
connected = true;
elementReader = new ElementReader(elementListener);
if (onConnected != null) {
onConnected.run();
}
}
@Override
public void send(ByteBuffer data) throws IOException {
logger.fine("Sending " + (data.capacity() - data.position()) + " bytes");
try {
onSendBlock.emit(data);
} catch (EncodingException e) {
logger.info("TLV decoding error: " + e.toString());
} catch (SecurityException e) {
logger.info("Signing error: " + e.toString());
}
}
@Override
public void processEvents() throws IOException, EncodingException {
if (!getIsConnected()) {
logger.warning("Not connnected...");
}
while (true) {
ByteBuffer block = null;
synchronized (m_recvBuffer) {
if (!m_recvBuffer.isEmpty()) {
block = m_recvBuffer.remove(0);
}
}
if (block == null) {
break;
}
elementReader.onReceivedData(block);
}
}
@Override
public boolean getIsConnected() {
return connected;
}
@Override
public void close() throws IOException {
logger.fine("Closing...");
connected = false;
}
//////////////////////////////////////////////////////////////////////////////
public OnSendBlockSignal onSendBlock;
//////////////////////////////////////////////////////////////////////////////
private static final Logger logger = Logger.getLogger(MockFaceTransport.class.getName());
private boolean connected;
private ElementReader elementReader;
private final List<ByteBuffer> m_recvBuffer = new LinkedList<ByteBuffer>();
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
/**
* Interests sent out of this MockFace
* <p>
* Sent Interests are appended to this container if options.enablePacketLogger is true.
* User of this class is responsible for cleaning up the container, if necessary.
* After .expressInterest, .processEvents must be called before the Interest would show up here.
*/
public List<Interest> sentInterests = new ArrayList<Interest>();
/**
* Data sent out of this MockFace
* <p>
* Sent Data are appended to this container if options.enablePacketLogger is true.
* User of this class is responsible for cleaning up the container, if necessary.
* After .put, .processEvents must be called before the Data would show up here.
*/
public List<Data> sentData = new ArrayList<Data>();
/**
* Emits whenever an Interest is sent
* <p>
* After .expressInterest, .processEvents must be called before this signal would be emitted.
*/
public List<SignalOnSendInterest> onSendInterest = new ArrayList<SignalOnSendInterest>();
/**
* Emits whenever a Data packet is sent
* <p>
* After .putData, .processEvents must be called before this signal would be emitted.
*/
public List<SignalOnSendData> onSendData = new ArrayList<SignalOnSendData>();
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
private static final Logger logger = Logger.getLogger(MockFace.class.getName());
private MockFaceTransport m_transport;
private KeyChain m_keychain;
}