package lbms.plugins.mldht.kad.messages;
import static the8472.bencode.Utils.buf2ary;
import the8472.bencode.BEncoder;
import the8472.bencode.Utils;
import lbms.plugins.mldht.kad.DHT;
import lbms.plugins.mldht.kad.GenericStorage;
import lbms.plugins.mldht.kad.GenericStorage.StorageItem;
import lbms.plugins.mldht.kad.Key;
import lbms.plugins.mldht.kad.messages.ErrorMessage.ErrorCode;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
public class PutRequest extends MessageBase {
/*
{
"a":
{
"cas": <optional expected seq-nr (int)>,
"id": <20 byte id of sending node (string)>,
"k": <ed25519 public key (32 bytes string)>,
"salt": <optional salt to be appended to "k" when hashing (string)>
"seq": <monotonically increasing sequence number (integer)>,
"sig": <ed25519 signature (64 bytes string)>,
"token": <write-token (string)>,
"v": <any bencoded type, whose encoded size < 1000>
},
"t": <transaction-id (string)>,
"y": "q",
"q": "put"
}
*/
long expectedSequenceNumber = -1;
long sequenceNumber = -1;
byte[] pubkey;
byte[] salt;
byte[] signature;
byte[] token;
byte[] value;
public PutRequest() {
super(null, Method.PUT, Type.REQ_MSG);
}
@Override
public Map<String, Object> getInnerMap() {
Objects.requireNonNull(token);
Objects.requireNonNull(value);
Objects.requireNonNull(id);
Map<String, Object> m = new TreeMap<>();
if(expectedSequenceNumber != -1)
m.put("cas", expectedSequenceNumber);
if(sequenceNumber != -1)
m.put("seq", sequenceNumber);
if(salt != null)
m.put("salt", salt);
if(pubkey != null)
m.put("k", pubkey);
if(signature != null)
m.put("sig", signature);
m.put("token", token);
m.put("v", new BEncoder.RawData(ByteBuffer.wrap(value)));
m.put("id", id.getHash());
return m;
}
public void populateFromStorage(StorageItem toPut) {
this.setValue(toPut.getRawValue());
if(toPut.mutable()) {
toPut.pubKey().map(Utils::buf2ary).ifPresent(this::setPubkey);
toPut.salt().map(Utils::buf2ary).ifPresent(this::setSalt);
toPut.sig().map(Utils::buf2ary).ifPresent(this::setSignature);
setSequenceNumber(toPut.seq());
}
}
@Override
public void apply(DHT dh_table) {
dh_table.put(this);
}
public boolean mutable() {
return pubkey != null;
}
public void validate() throws MessageException {
if(salt != null && salt.length > 64)
throw new MessageException("salt too long", ErrorCode.SaltTooBig);
if(token == null || value == null)
throw new MessageException("required arguments for PUT request missing", ErrorCode.ProtocolError);
if(value.length > 1000)
throw new MessageException("bencoded PUT value ('v') field exceeds 1000 bytes", ErrorCode.PutMessageTooBig);
if((pubkey != null || salt != null || signature != null || expectedSequenceNumber >= 0 || sequenceNumber >= 0) && (pubkey == null || signature == null))
throw new MessageException("PUT request contained at least one field indicating mutable data but other fields mandatory for mutable PUTs were missing", ErrorCode.ProtocolError);
}
public byte[] getToken() {
return token;
}
public long getSequenceNumber() {
return sequenceNumber;
}
public byte[] getPubkey() {
return pubkey;
}
public byte[] getSalt() {
return salt;
}
public byte[] getSignature() {
return signature;
}
public Key deriveTargetKey() {
return GenericStorage.fingerprint(pubkey, salt, getValue());
}
public long getExpectedSequenceNumber() {
return expectedSequenceNumber;
}
public void setExpectedSequenceNumber(long expectedSequenceNumber) {
this.expectedSequenceNumber = expectedSequenceNumber;
}
public ByteBuffer getValue() {
return ByteBuffer.wrap(value);
}
public void setValue(ByteBuffer value) {
this.value = buf2ary(value);
}
public void setSequenceNumber(long sequenceNumber) {
this.sequenceNumber = sequenceNumber;
}
public void setPubkey(byte[] pubkey) {
this.pubkey = pubkey;
}
public void setSalt(byte[] salt) {
this.salt = salt;
}
public void setSignature(byte[] signature) {
this.signature = signature;
}
public void setToken(byte[] token) {
this.token = token;
}
}