All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.coinspark.protocol.CoinSparkMessage Maven / Gradle / Ivy

/*
 * CoinSpark 2.1 - Java library
 *
 * Copyright (c) Coin Sciences Ltd
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package org.coinspark.protocol;

import java.util.Arrays;

/**
 * CoinSparkMessage class for managing CoinSpark messages
 */


public class CoinSparkMessage extends CoinSparkBase{
    
// Public functions    

    /**
     * Returns Server Host
     * 
     * @return Server Host
     */

    public String getServerHost() {
        return serverHost;
    }

    /**
     * Sets DomainName
     * 
     * @param ServerHost to set
     */
    
    public void setServerHost(String ServerHost) {
        serverHost = ServerHost;
    }

    /**
     * Returns Server Path
     * 
     * @return Server Path
     */

    public String getServerPath() {
        return serverPath;
    }

    /**
     * Sets Server Path
     * 
     * @param Server to set
     */
    
    public void setServerPath(String Server) {
        serverPath = Server;
    }

    /**
     * Returns Use https flag
     * 
     * @return Use https flag
     */

    public boolean getUseHttps() {
        return useHttps;
    }

    /**
     * Sets Use https flag
     * 
     * @param UseHttps to set
     */
    
    public void setUseHttps(boolean UseHttps) {
        useHttps = UseHttps;
    }

    /**
     * Returns Use path prefix flag
     * 
     * @return Use path prefix flag
     */

    public boolean getUsePrefix() {
        return usePrefix;
    }

    /**
     * Sets Use path prefix flag
     * 
     * @param UsePrefix flag to set
     */
    
    public void setUsePrefix(boolean UsePrefix) {
        usePrefix = UsePrefix;
    }

    /**
     * Sets all components of the server URL
     * 
     * @param DomainPath 
     */
    
    public void setDomainPath(CoinSparkDomainPath DomainPath)
    {
        serverHost=DomainPath.domainName;
        serverPath=DomainPath.path;
        useHttps=DomainPath.useHttps;
        usePrefix=DomainPath.usePrefix;
    }
    
    /**
     * Returns is public flag
     * 
     * @return is public flag
     */

    public boolean getIsPublic() {
        return isPublic;
    }

    /**
     * Sets is public flag
     * 
     * @param IsPublic flag to set
     */
    
    public void setIsPublic(boolean IsPublic) {
        isPublic = IsPublic;
    }
    /**
     * Returns Message hash
     * 
     * @return Message hash
     */

    public byte [] getHash() {
        return hash;
    }
    
    /**
     * Returns Message hash length (to be) encoded
     * 
     * @return Message hash length
     */

    public int getHashLen() {
        return hashLen;
    }
    
    /**
     * Sets message hash.
     * 
     * @param Hash to set
     */
    
    public void setHash(byte [] Hash) {
	if (Hash==null) {
	    hash=null;
	} else {
	    hash=Arrays.copyOf(Hash, Hash.length);
	}
    }
    
    /**
     * Sets message hash length.
     * 
     * @param HashLen to set
     */
    
    public void setHashLen(int HashLen) {
        hashLen=HashLen;
    }
    
    /**
     * Returns array of output ranges
     * 
     * @return array of output ranges
     */
    
    public CoinSparkIORange [] getOutputRanges()
    {
        return Arrays.copyOf(outputRanges,countOutputRanges);
    }

    /**
     * Sets array of output ranges
     * 
     * @param OutputRanges to set
     */
    
    public void setOutputRanges(CoinSparkIORange [] OutputRanges)
    {
        countOutputRanges=OutputRanges.length;
        outputRanges=Arrays.copyOf(OutputRanges, countOutputRanges);
    }
    
    /**
     * Adds OutputRange to the list
     * 
     * @param OutputRange to add
     */
    
    public void addOutputs(CoinSparkIORange OutputRange)
    {
        if (COINSPARK_MESSAGE_MAX_IO_RANGES > countOutputRanges)
        {
            outputRanges[countOutputRanges] = OutputRange;
            countOutputRanges++;
        }        
    }
    
    /**
     * CoinSparkMessage class for managing CoinSpark messages
     */

    public CoinSparkMessage()
    {       
        clear();
    }

    /**
     * Set all fields in address to their default/zero values, which are not necessarily valid.
     */
    
    public final void clear()
    {        
        useHttps = false;
        serverHost = ""; 
        usePrefix = false; 
        serverPath = "";  
        isPublic = false;
        outputRanges = new CoinSparkIORange[COINSPARK_MESSAGE_MAX_IO_RANGES]; 
        countOutputRanges = 0;
        hash = new byte[COINSPARK_MESSAGE_HASH_MAX_LEN]; 
        hashLen = 0; 
    }
    
    /**
     * Returns full URL of delivery server.
     * 
     * @return full URL of delivery server.
     */
    
    public String getFullURL()
    {
        return (new CoinSparkDomainPath(serverHost, serverPath, useHttps, usePrefix)).getFullURL();
    }
    
    @Override
    public String toString()
    {
        CoinSparkBuffer assetWebPageBuffer=new CoinSparkBuffer();
        String encodedWebPage="";
                
        CoinSparkDomainPath assetWebPage=new CoinSparkDomainPath(serverHost, serverPath, useHttps, usePrefix);
        if(assetWebPage.encode(assetWebPageBuffer,true))
        {
            encodedWebPage=assetWebPageBuffer.toHex();
        }
        
        String urlString=calcServerUrl();
        
        StringBuilder sb = new StringBuilder();
        sb.append("COINSPARK MESSAGE\n");
        sb.append(String.format("    Server URL: %s (length %d+%d encoded %s length %d)\n", 
                urlString,
                serverHost.length(),
                serverPath.length(),
                encodedWebPage,
                assetWebPage.encodedLen(true)));
        sb.append(String.format("Public message: %s\n", isPublic ? "yes" : "no"));

        for (int index=0; index < countOutputRanges; index++)
        {
            if(outputRanges[index].count > 0)
            {
                if(outputRanges[index].count > 1)
                {
                    sb.append(String.format("       Outputs: %d - %d (count %d)", 
                            outputRanges[index].first,
                            outputRanges[index].first+outputRanges[index].count-1,
                            outputRanges[index].count));                    
                }
                else
                {
                    sb.append(String.format("        Output: %d",outputRanges[index].first));
                }
                sb.append(String.format(" (small endian hex: first %s count %s)\n",
                    unsignedToSmallEndianHex(outputRanges[index].first, 2),
                    unsignedToSmallEndianHex(outputRanges[index].count, 2))); 
            }
            else
            {
                sb.append("       Outputs: none");                
            }

        }
        
        sb.append(String.format("  Message hash: "));
        sb.append(byteToHex(Arrays.copyOf(hash, hashLen)));                
        sb.append(String.format(" (length %d)\n", hashLen));

        sb.append("END COINSPARK MESSAGE\n\n");
        return sb.toString();
    }
    
    /**
     * Returns true if all values in the message are in their permitted ranges, false otherwise.
     * 
     * @return true if genesis structure is valid
     */
    
    public boolean isValid()
    {			
        if (serverHost.length() > COINSPARK_MESSAGE_SERVER_HOST_MAX_LEN)
            return false;
	
        if (serverPath.length()>COINSPARK_MESSAGE_SERVER_PATH_MAX_LEN)
            return false;
				
        if (hash.length < hashLen)                                              // check we have at least as much data as specified by $this->hashLen
            return false; 
	
        if ( (hashLenCOINSPARK_MESSAGE_HASH_MAX_LEN) )
            return false;

        if ( (!isPublic) && (countOutputRanges == 0) )                          // public or aimed at some outputs at least
            return false;
	
        if (countOutputRanges > COINSPARK_MESSAGE_MAX_IO_RANGES)
            return false;
	
        for (int index=0; index < countOutputRanges; index++)
        {
            if(!outputRanges[index].isValid())
            {
                return false;
            }
        }

        return true;
    }
		
    /**
     * Returns true if the two CoinSparkMessage structures are the same. If strict is true then
     * the OutputRanges must be identical.
     * If strict is false then only normalized OutputRanges must be identical.
     *
     * @param message2 CoinSparkMessage to compare with
     * @param strict Strict comparison flag
     * @return true if two CoinSparkMessage match, false otherwise
     */
    
    
    public boolean match(CoinSparkMessage message2, boolean strict)
    {
        int hashCompareLen=Math.min(Math.min(hashLen, message2.getHashLen()), COINSPARK_MESSAGE_HASH_MAX_LEN);
	
        CoinSparkIORange [] thisRanges;
        CoinSparkIORange [] otherRanges;
        
        
        thisRanges = getOutputRanges();
        otherRanges = message2.getOutputRanges();
        if(!strict)
        {
            thisRanges=CoinSparkIORange.normalizeIORanges(thisRanges);
            otherRanges=CoinSparkIORange.normalizeIORanges(otherRanges);
        }
        
        if (thisRanges.length != otherRanges.length)
            return false;
				
        for (int index=0; index < thisRanges.length; index++)
        {
            if(!thisRanges[index].match(otherRanges[index]))
                return false;
        }
        
        byte [] otherHash=message2.getHash();
        for(int index=0;index 0)
        {
            System.arraycopy(salt, 0, buffer, offset, salt.length);
        }
        offset += salt.length+1;buffer[offset-1]=0x00;
        for(CoinSparkMessagePart part : messageParts)
        {
            if((part.mimeType != null) && (part.mimeType.length() > 0))
            {
                System.arraycopy(part.mimeType.getBytes(), 0, buffer, offset, part.mimeType.length());
                offset += part.mimeType.length();
            }
            offset += 1;buffer[offset-1]=0x00;
            if((part.fileName != null) && (part.fileName.length() > 0))
            {
                System.arraycopy(part.fileName.getBytes(), 0, buffer, offset, part.fileName.length());
                offset += part.fileName.length();
            }
            offset += 1;buffer[offset-1]=0x00;
            if(part.content.length>0)
            {
                System.arraycopy(part.content, 0, buffer, offset, part.content.length);
            }
            offset += part.content.length+1;buffer[offset-1]=0x00;
        }        
        
        return coinSparkCalcSHA256Hash(buffer, offset);
    }
    
    /**
     * Returns true if message has specified output in its ranges
     * 
     * @param outputIndex output index to check
     * @return true if message has specified output in its ranges, false otherwise
     */
    
    public boolean hasOutput(int outputIndex)
    {
        for (int index=0; index < countOutputRanges; index++)
        {
            if ( (outputIndex>=outputRanges[index].first) && (outputIndex<(outputRanges[index].first+outputRanges[index].count)) )
                    return true;
        }
        return false;
    }
		
    /**
     * Calculates the appropriate message hash length so that when encoded as metadata the genesis will
     * fit in metadataMaxLen bytes. For now, set metadataMaxLen to 40 (see Bitcoin's MAX_OP_RETURN_RELAY parameter).
     *
     * @param metadataMaxLen metadata maximal length
     * @return asset hash length of message
     */
    
    /**
     * Calculates the appropriate message hash length so that when encoded as metadata the genesis will
     * fit in metadataMaxLen bytes.For now, set metadataMaxLen to 40 (see Bitcoin's MAX_OP_RETURN_RELAY parameter).
     * 
     * @param countOutputs number of outputs in transaction
     * @param metadataMaxLen metadata maximal length
     * @return hash length of the message
     */
    
    public int calcHashLen(int countOutputs, int metadataMaxLen)
    {
        int len = metadataMaxLen-COINSPARK_METADATA_IDENTIFIER.length()-1;
	
        CoinSparkDomainPath assetWebPage=new CoinSparkDomainPath(serverHost, serverPath, useHttps, usePrefix);
        len-=assetWebPage.encodedLen(true);
	
	if (isPublic)
            len--;
		
        for (int index=0; index < countOutputRanges; index++)
        {
            int [] result = getOutputRangePacking(outputRanges[index], countOutputs);
            
            if(result != null)
            {
                len -= (1 + result[1] +result[2]);
            }
        }        
        
        if (len > COINSPARK_MESSAGE_HASH_MAX_LEN)
            len = COINSPARK_MESSAGE_HASH_MAX_LEN;

        return len;
    }
		
    
    
// Private variables/constants/functions   
        
    private boolean useHttps;
    private String serverHost; 
    private boolean usePrefix; 
    private String serverPath;  
    private boolean isPublic;
    CoinSparkIORange [] outputRanges; 
    private int countOutputRanges;
    private byte[] hash; 
    private int hashLen; 
    

    private static final int COINSPARK_MESSAGE_SERVER_HOST_MAX_LEN=32;
    private static final int COINSPARK_MESSAGE_SERVER_PATH_MAX_LEN=24;
    private static final int COINSPARK_MESSAGE_HASH_MIN_LEN=12;
    private static final int COINSPARK_MESSAGE_HASH_MAX_LEN=32;
    private static final int COINSPARK_MESSAGE_MAX_IO_RANGES=16;


    private static final int COINSPARK_OUTPUTS_MORE_FLAG=0x80;
    private static final int COINSPARK_OUTPUTS_RESERVED_MASK=0x60;
    private static final int COINSPARK_OUTPUTS_TYPE_MASK=0x18;
    private static final int COINSPARK_OUTPUTS_TYPE_SINGLE=0x00; // one output index (0...7)
    private static final int COINSPARK_OUTPUTS_TYPE_FIRST=0x08; // first (0...7) outputs
    private static final int COINSPARK_OUTPUTS_TYPE_UNUSED=0x10; // for future use
    private static final int COINSPARK_OUTPUTS_TYPE_EXTEND=0x18; // "extend", including public/all
    private static final int COINSPARK_OUTPUTS_VALUE_MASK=0x07;
    private static final int COINSPARK_OUTPUTS_VALUE_MAX=7;

    private String calcServerUrl()
    {
        String s="";
        
        s += useHttps ? "https" : "http";
        s += "://" + serverHost + "/";
        s += usePrefix ? "coinspark/" : "";
        s += serverPath;
        s += (serverPath.length()>0) ? "/" : "";
                        
        return s.toLowerCase();
    }

    private int [] getOutputRangePacking(CoinSparkIORange outputRange, int countOutputs)
    {
        int [] result=new int[3];
        int packing;
        Byte packingExtend;
        
        boolean [] packingOptions=CoinSparkPacking.getPackingOptions(null, outputRange, countOutputs, true);

        result[1]=0;
        result[2]=0;

        if (packingOptions[CoinSparkPacking.PackingType._1_0_BYTE.getValue()] && (outputRange.first<=COINSPARK_OUTPUTS_VALUE_MAX)) // inline single output
            packing=COINSPARK_OUTPUTS_TYPE_SINGLE | (outputRange.first & COINSPARK_OUTPUTS_VALUE_MASK);

        else if (packingOptions[CoinSparkPacking.PackingType._0_1_BYTE.getValue()] && (outputRange.count<=COINSPARK_OUTPUTS_VALUE_MAX)) // inline first few outputs
            packing=COINSPARK_OUTPUTS_TYPE_FIRST | (outputRange.count & COINSPARK_OUTPUTS_VALUE_MASK);

        else 
        {                                                                       // we'll be taking additional bytes
            packingExtend=CoinSparkPacking.encodePackingExtend(packingOptions);
            if (packingExtend == null)
                return null;

            result=CoinSparkPacking.packingExtendAddByteCounts(packingExtend, result[1], result[2], true);

            packing=COINSPARK_OUTPUTS_TYPE_EXTEND | (packingExtend & COINSPARK_OUTPUTS_VALUE_MASK);
        }

        result[0]=packing;
        return result;
    }		
    
    private boolean encode(CoinSparkBuffer buffer,int countOutputs,int metadataMaxLen)
    {
        int packing,packingExtend;
        
        try
        {
            if (!isValid())
                throw new CoinSparkExceptions.CannotEncode("invalid message");
        
            //  4-character identifier

            buffer.writeString(COINSPARK_METADATA_IDENTIFIER);
            buffer.writeByte(COINSPARK_MESSAGE_PREFIX);
		
            //  Server host and path

            CoinSparkDomainPath assetWebPage=new CoinSparkDomainPath(serverHost, serverPath, useHttps, usePrefix);
            if (!assetWebPage.encode(buffer,true))
                throw new CoinSparkExceptions.CannotEncode("cannot write domain name/path");
	
            //  Output ranges
	
            if (isPublic) 
            {                                                                   // add public indicator first
                packing=((countOutputRanges>0) ? COINSPARK_OUTPUTS_MORE_FLAG : 0) |
                                COINSPARK_OUTPUTS_TYPE_EXTEND | CoinSparkPacking.COINSPARK_PACKING_EXTEND_PUBLIC;
                buffer.writeInt(packing, 1);
            }
			
            for (int index=0; index < countOutputRanges; index++)
            {
                int firstBytes,countBytes;

                int [] result=getOutputRangePacking(outputRanges[index], countOutputs);
                if(result == null)
                    throw new CoinSparkExceptions.CannotEncode("invalid range");
                
                packing=result[0];
                firstBytes=result[1];
                countBytes=result[2];                
                
                //  The packing byte

                if ((index+1)metadataMaxLen)
                throw new CoinSparkExceptions.CannotEncode("total length above limit");		
        
        }
        catch (Exception ex)
        {
            System.out.print(ex.getMessage());
            return false;
        }
        
        return true;
    }    
    
    private boolean decode(CoinSparkBuffer buffer,int countOutputs)
    {
        if(!buffer.locateRange(COINSPARK_MESSAGE_PREFIX))
            return false;

        try
        {            
            //  Server host and path
            CoinSparkDomainPath assetWebPage=new CoinSparkDomainPath(serverHost, serverPath, useHttps, usePrefix);
            if (!assetWebPage.decode(buffer,true))
                throw new CoinSparkExceptions.CannotDecode("cannot decode server host");
            
            serverHost=assetWebPage.domainName;
            serverPath=assetWebPage.path;
            useHttps=assetWebPage.useHttps;
            usePrefix=assetWebPage.usePrefix;
            
            //  Output ranges
	
            isPublic=false;
            outputRanges=new CoinSparkIORange[COINSPARK_MESSAGE_MAX_IO_RANGES];
            countOutputRanges=0;
	            
            int packing = COINSPARK_OUTPUTS_MORE_FLAG;            
            
            while((packing & COINSPARK_OUTPUTS_MORE_FLAG) > 0)
            {                
                if(buffer.canRead(1))
                {
                    packing=buffer.readInt(1);                                  //  Read the next packing byte and check reserved bits are zero
                }
                else
                    throw new CoinSparkExceptions.CannotDecode("Cannot read packing");

                if((packing & COINSPARK_OUTPUTS_RESERVED_MASK) > 0)
                    throw new CoinSparkExceptions.CannotDecode("reserved bits used in packing");
                		
		int packingType=packing & COINSPARK_OUTPUTS_TYPE_MASK;
                int packingValue=packing & COINSPARK_OUTPUTS_VALUE_MASK;
		
		if ((packingType==COINSPARK_OUTPUTS_TYPE_EXTEND) && (packingValue==CoinSparkPacking.COINSPARK_PACKING_EXTEND_PUBLIC))
                {
                    isPublic=true;                                              // special case for public messages
                }
                else 
                {		
                                                                                //  Create a new output range			
                    if (countOutputRanges>=COINSPARK_MESSAGE_MAX_IO_RANGES)     // too many output ranges
                        throw new CoinSparkExceptions.CannotDecode("too many output ranges");
					
                    int firstBytes=0;
                    int countBytes=0;			
                    CoinSparkIORange outputRange;
                                                                                //  Decode packing byte			
                    if (packingType==COINSPARK_OUTPUTS_TYPE_SINGLE)             // inline single input
                    {                                                           
                        outputRange=new CoinSparkIORange();
                        outputRange.first=packingValue;
                        outputRange.count=1;
                    } 
                    else if (packingType==COINSPARK_OUTPUTS_TYPE_FIRST)         // inline first few outputs
                    { 
                        outputRange=new CoinSparkIORange();
                        outputRange.first=0;
                        outputRange.count=packingValue;
                    } 
                    else if (packingType==COINSPARK_OUTPUTS_TYPE_EXTEND)        // we'll be taking additional bytes
                    { 
                        CoinSparkPacking.PackingType extendPackingType;
			extendPackingType=CoinSparkPacking.decodePackingExtend((byte)packingValue, true);
                        if (extendPackingType == CoinSparkPacking.PackingType._NONE)
                            throw new CoinSparkExceptions.CannotDecode("Wrong packing type");
						
                        outputRange=CoinSparkPacking.packingTypeToValues(extendPackingType, null, countOutputs);

                        int result [] =CoinSparkPacking.packingExtendAddByteCounts(packingValue, firstBytes, countBytes, true);
                        firstBytes = result[1];				
                        countBytes = result[2];				
                    } 
                    else
                        throw new CoinSparkExceptions.CannotDecode("unused packing type");
                        
			
                    //  The index of the first output and number of outputs, if necessary
					
                    if (firstBytes>0)
                    {
                        if(buffer.canRead(firstBytes))
                            outputRange.first=buffer.readInt(firstBytes);
                        else
                            throw new CoinSparkExceptions.CannotDecode("Cannot read first");
                    }

                    if (countBytes>0)
                    {
                        if(buffer.canRead(countBytes))
                            outputRange.count=buffer.readInt(countBytes);
                        else
                            throw new CoinSparkExceptions.CannotDecode("Cannot read count");
                    }
                    
                    outputRanges[countOutputRanges]=outputRange;			//	Add on the new output range
				
                    countOutputRanges++;
                }
                
            }
            
            //  Message hash

            hashLen = buffer.availableForRead();//TBD loss
            hashLen = Math.min(hashLen, COINSPARK_MESSAGE_HASH_MAX_LEN);        // apply maximum

            if (hashLen < COINSPARK_MESSAGE_HASH_MIN_LEN)                       // not enough hash data                
                throw new CoinSparkExceptions.CannotDecode("has data out of range");

            hash=buffer.readBytes(hashLen);            
        }
        catch (Exception ex)
        {
            System.out.print(ex.getMessage());
            return false;
        }
        
        return isValid();
        
			
	
    }

    
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy