convex.net.Message Maven / Gradle / Ivy
package convex.net;
import java.io.IOException;
import java.util.function.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import convex.core.Belief;
import convex.core.ErrorCodes;
import convex.core.Result;
import convex.core.SourceCodes;
import convex.core.data.ACell;
import convex.core.data.AString;
import convex.core.data.AVector;
import convex.core.data.Blob;
import convex.core.data.Format;
import convex.core.data.Hash;
import convex.core.data.Ref;
import convex.core.data.SignedData;
import convex.core.data.Strings;
import convex.core.data.Tag;
import convex.core.data.Vectors;
import convex.core.data.prim.CVMLong;
import convex.core.exceptions.BadFormatException;
import convex.core.exceptions.MissingDataException;
import convex.core.lang.RT;
import convex.core.store.AStore;
/**
* Class representing a message to / from a specific connection
*
* Encapsulates both message content and a means of return communication
.
*
* This class is an immutable data structure, but NOT a representable on-chain
* data structure, as it is part of the peer protocol layer.
*
* Messages may contain a Payload, which can be any Data Value.
*/
public class Message {
protected static final Logger log = LoggerFactory.getLogger(Message.class.getName());
protected ACell payload;
protected Blob messageData; // encoding of payload (possibly multi-cell)
protected MessageType type;
protected Predicate returnHandler;
protected Message(MessageType type, ACell payload, Blob data, Predicate handler) {
this.type = type;
this.messageData=data;
this.payload = payload;
this.returnHandler=handler;
}
public static Message create(Connection conn, MessageType type, Blob data) {
Predicate handler=t -> {
try {
return conn.sendMessage(t);
} catch (IOException e) {
return false;
}
};
return new Message(type, null,data,handler);
}
public static Message create(MessageType type,ACell payload) {
return new Message(type, payload,null,null);
}
public static Message create(MessageType type,ACell payload, Blob data) {
return new Message(type, payload,data,null);
}
public static Message createDataResponse(CVMLong id, ACell... cells) {
int n=cells.length;
ACell[] cs=new ACell[n+1];
cs[0]=id;
for (int i=0; i challenge) {
return create(MessageType.CHALLENGE, challenge);
}
public static Message createResponse(SignedData response) {
return create(MessageType.RESPONSE, response);
}
public static Message createGoodBye() {
return create(MessageType.GOODBYE, null);
}
@SuppressWarnings("unchecked")
public T getPayload() throws BadFormatException {
if (payload!=null) return (T) payload;
if (messageData==null) return null; // no message data, so must actually be null
// detect actual message data for null payload :-)
if ((messageData.count()==1)&&(messageData.byteAt(0)==Tag.NULL)) return null;
switch(type) {
case MessageType.DATA:
ACell[] cells=Format.decodeCells(messageData);
payload=Vectors.create(cells);
break;
default:
payload=Format.decodeMultiCell(messageData);
}
return (T) payload;
}
/**
* Gets the encoded data for this message. Generates a single cell encoding if required.
* @return Blob containing message data
*/
public Blob getMessageData() {
if (messageData!=null) return messageData;
// default to single cell encoding
// TODO: alternative depths for different types
switch (type) {
case MessageType.RESULT:
case MessageType.QUERY:
case MessageType.TRANSACT:
case MessageType.REQUEST_DATA:
messageData=Format.encodeMultiCell(payload,true);
break;
case MessageType.DATA:
@SuppressWarnings("unchecked")
AVector v=(AVector) payload;
messageData=Format.encodeCells(v);
break;
default:
messageData=Format.encodedBlob(payload);
}
return messageData;
}
public MessageType getType() {
return type;
}
@Override
public String toString() {
try {
ACell payload=getPayload();
AString ps=RT.print(getPayload(),10000);
if (ps==null) ps=Strings.create("<"+RT.count(messageData)+" bytes as "+RT.getType(payload)+">");
return "#message {:type " + getType() + " :payload " + ps + "}";
} catch (MissingDataException e) {
return "#message {:type " + getType() + " :payload }";
} catch (BadFormatException e) {
return "#message )getPayload()).get(0);
// Result is a special record type
case RESULT: return (CVMLong)((Result)getPayload()).getID();
// Status ID is the single value
case STATUS: return (CVMLong)(getPayload());
case DATA: {
ACell o=getPayload();
if (o instanceof AVector) {
AVector> v = (AVector>)o;
if (v.count()==0) return null;
// first element might be ID, otherwise null
return RT.ensureLong(v.get(0));
}
}
default: return null;
}
} catch (BadFormatException e) {
return null;
}
}
/**
* Sets the message ID, if supported
* @param id ID to set for message
* @return Message with updated ID, or null if message does not support IDs
*/
@SuppressWarnings("unchecked")
public Message withID(CVMLong id) {
try {
switch (type) {
// Query and transact use a vector [ID ...]
case QUERY:
case TRANSACT:
return Message.create(type, ((AVector)getPayload()).assoc(0, id));
// Result is a special record type
case RESULT:
return Message.create(type, ((Result)getPayload()).withID(id));
// Status ID is the single value
case STATUS:
return Message.create(type, id);
case DATA: {
ACell o=getPayload();
if (o instanceof AVector) {
AVector v = (AVector)o;
if (v.count()==0) return null;
// first element assumed to be ID
return Message.create(type, v.assoc(0, id));
}
}
default: return null;
}
} catch (BadFormatException | ClassCastException | IndexOutOfBoundsException e) {
return null;
}
}
/**
* Reports a result back to the originator of the message.
*
* Will set a Result ID if necessary.
*
* @param res Result record
* @return True if reported successfully, false otherwise
*/
public boolean returnResult(Result res) {
ACell id=getID();
if (id!=null) res=res.withID(id);
Message msg=Message.createResult(res);
return returnMessage(msg);
}
/**
* Returns a message back to the originator of the message.
*
* Will set response ID if necessary.
*
* @param m Message
* @return True if sent successfully, false otherwise
*/
public boolean returnMessage(Message m) {
Predicate handler=returnHandler;
if (handler==null) return false;
return handler.test(m);
}
public boolean hasData() {
return messageData!=null;
}
public static Message createResult(Result res) {
return create(MessageType.RESULT,res);
}
public static Message createResult(CVMLong id, ACell value, ACell error) {
Result r=Result.create(id, value,error);
return createResult(r);
}
/**
* Closes any connection associated with this message, probably because of bad behaviour
*/
public void closeConnection() {
returnHandler=null;
}
public Message makeDataResponse(AStore store) throws BadFormatException {
AVector v = RT.ensureVector(getPayload());
if ((v == null)||(v.isEmpty())) {
throw new BadFormatException("Invalid data request payload");
};
//System.out.println("DATA REQ:"+ v);
long n=v.count();
for (int i=1; i r = store.refForHash(h);
if (r != null) {
ACell data = r.getValue();
v=v.assoc(i, data);
} else {
// signal we don't have this data
v=v.assoc(i, null);
}
}
//System.out.println("DATA RESP:"+ v);
// Create response. Will have null return connection
Message resp=create(MessageType.DATA,v);
return resp;
}
public Result toResult() {
try {
MessageType type=getType();
switch (type) {
case MessageType.RESULT:
Result result=getPayload();
return result;
default:
return Result.create(getID(), Strings.create("Unexpected message type for Result: "+type), ErrorCodes.UNEXPECTED);
}
} catch (BadFormatException e) {
return Result.fromException(e).withSource(SourceCodes.CLIENT);
}
}
/**
* Create an instance with the given message data
* @param type Message type
* @param payload Message payload
* @param handler Handler for Results
* @return New MessageLocal instance
*/
public static Message create(MessageType type, ACell payload, Predicate handler) {
return new Message(type,payload,null,handler);
}
}