'use strict'
/**
* @module types
*/
const TypeRegistry = require('./type-registry')
const Condition = require('./condition')
const Predictor = require('./predictor')
const Writer = require('./writer')
const Reader = require('./reader')
const base64url = require('../util/base64url')
const PrefixError = require('../errors/prefix-error')
const ParseError = require('../errors/parse-error')
// Regex for validating fulfillments
//
// This is a generic, future-proof version of the fulfillment regular
// expression.
const FULFILLMENT_REGEX = /^cf:([1-9a-f][0-9a-f]{0,3}|0):[a-zA-Z0-9_-]*$/
/**
* Base class for fulfillment types.
*/
class Fulfillment {
/**
* Create a Fulfillment object from a URI.
*
* This method will parse a fulfillment URI and construct a corresponding
* Fulfillment object.
*
* @param {String} serializedFulfillment URI representing the fulfillment
* @return {Fulfillment} Resulting object
*/
static fromUri (serializedFulfillment) {
if (serializedFulfillment instanceof Fulfillment) {
return serializedFulfillment
} else if (typeof serializedFulfillment !== 'string') {
throw new Error('Serialized fulfillment must be a string')
}
const pieces = serializedFulfillment.split(':')
if (pieces[0] !== 'cf') {
throw new PrefixError('Serialized fulfillment must start with "cf:"')
}
if (!Fulfillment.REGEX.exec(serializedFulfillment)) {
throw new ParseError('Invalid fulfillment format')
}
const typeId = parseInt(pieces[1], 16)
const payload = base64url.decode(pieces[2])
const ConditionClass = TypeRegistry.getClassFromTypeId(typeId)
const fulfillment = new ConditionClass()
fulfillment.parsePayload(Reader.from(payload), payload.length)
return fulfillment
}
/**
* Create a Fulfillment object from a binary blob.
*
* This method will parse a stream of binary data and construct a
* corresponding Fulfillment object.
*
* @param {Reader} reader Binary stream implementing the Reader interface
* @return {Fulfillment} Resulting object
*/
static fromBinary (reader) {
reader = Reader.from(reader)
const ConditionClass = TypeRegistry.getClassFromTypeId(reader.readUInt16())
const condition = new ConditionClass()
const payloadLength = reader.readLengthPrefix()
condition.parsePayload(reader, payloadLength)
return condition
}
/**
* Return the type ID of this fulfillment.
*
* @return {Number} Type ID as an integer.
*/
getTypeId () {
return this.constructor.TYPE_ID
}
/**
* Return the bitmask of this fulfillment.
*
* For simple fulfillment types this is simply the bit representing this type.
*
* For meta-fulfillments, these are the bits representing the types of the
* subconditions.
*
* @return {Number} Bitmask corresponding to this fulfillment.
*/
getBitmask () {
return this.constructor.FEATURE_BITMASK
}
/**
* Generate condition corresponding to this fulfillment.
*
* An important property of crypto-conditions is that the condition can always
* be derived from the fulfillment. This makes it very easy to post
* fulfillments to a system without having to specify which condition the
* relate to. The system can keep an index of conditions and look up any
* matching events related to that condition.
*
* @return {Condition} Condition corresponding to this fulfillment.
*/
getCondition () {
const condition = new Condition()
condition.setTypeId(this.getTypeId())
condition.setBitmask(this.getBitmask())
condition.setHash(this.generateHash())
condition.setMaxFulfillmentLength(this.calculateMaxFulfillmentLength())
return condition
}
/**
* Shorthand for getting condition URI.
*
* Stands for getCondition().serializeUri().
*
* @return {String} Condition URI.
*/
getConditionUri () {
return this.getCondition().serializeUri()
}
/**
* Shorthand for getting condition encoded as binary.
*
* Stands for getCondition().serializeBinary().
*
* @return {Buffer} Binary encoded condition.
*/
getConditionBinary () {
return this.getCondition().serializeBinary()
}
/**
* Generate the hash of the fulfillment.
*
* This method is a stub and will be overridden by subclasses.
*
* @return {Buffer} Fingerprint of the condition.
*
* @private
*/
generateHash () {
throw new Error('This method should be implemented by a subclass')
}
/**
* Calculate the maximum length of the fulfillment payload.
*
* This implementation works by measuring the length of the fulfillment.
* Condition types that do not have a constant length will override this
* method with one that calculates the maximum possible length.
*
* @return {Number} Maximum fulfillment length
*
* @private
*/
calculateMaxFulfillmentLength () {
const predictor = new Predictor()
this.writePayload(predictor)
return predictor.getSize()
}
/**
* Generate the URI form encoding of this fulfillment.
*
* Turns the fulfillment into a URI containing only URL-safe characters. This
* format is convenient for passing around fulfillments in URLs, JSON and
* other text-based formats.
*
* @return {String} Fulfillment as a URI
*/
serializeUri () {
return 'cf' +
':' + this.getTypeId().toString(16) +
':' + base64url.encode(this.serializePayload())
}
/**
* Serialize fulfillment to a buffer.
*
* Encodes the fulfillment as a string of bytes. This is used internally for
* encoding subfulfillments, but can also be used to passing around
* fulfillments in a binary protocol for instance.
*
* @return {Buffer} Serialized fulfillment
*/
serializeBinary () {
const writer = new Writer()
writer.writeUInt16(this.getTypeId())
writer.writeVarOctetString(this.serializePayload())
return writer.getBuffer()
}
/**
* Return the fulfillment payload as a buffer.
*
* Note that the fulfillment payload is not the standard format for passing
* fulfillments in binary protocols. Use `serializeBinary` for that. The
* fulfillment payload is purely the type-specific data and does not include
* the bitmask.
*
* @return {Buffer} Fulfillment payload
*
* @private
*/
serializePayload () {
const writer = new Writer()
this.writePayload(writer)
return writer.getBuffer()
}
/**
* Validate this fulfillment.
*
* This implementation is a stub and will be overridden by the subclasses.
*
* @return {Boolean} Validation result
*/
validate () {
throw new Error('Not implemented')
}
}
Fulfillment.REGEX = FULFILLMENT_REGEX
module.exports = Fulfillment