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

com.github.skjolber.ndef.Record Maven / Gradle / Ivy

/***************************************************************************
 * 
 * This file is part of the 'NDEF Tools for Android' project at
 * http://code.google.com/p/ndef-tools-for-android/
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 ****************************************************************************/

package com.github.skjolber.ndef;

import java.util.Arrays;

import com.github.skjolber.ndef.externaltype.ExternalTypeRecord;
import com.github.skjolber.ndef.wellknown.ActionRecord;
import com.github.skjolber.ndef.wellknown.GcActionRecord;
import com.github.skjolber.ndef.wellknown.GcDataRecord;
import com.github.skjolber.ndef.wellknown.GcTargetRecord;
import com.github.skjolber.ndef.wellknown.GenericControlRecord;
import com.github.skjolber.ndef.wellknown.SignatureRecord;
import com.github.skjolber.ndef.wellknown.SmartPosterRecord;
import com.github.skjolber.ndef.wellknown.TextRecord;
import com.github.skjolber.ndef.wellknown.UriRecord;
import com.github.skjolber.ndef.wellknown.handover.AlternativeCarrierRecord;
import com.github.skjolber.ndef.wellknown.handover.CollisionResolutionRecord;
import com.github.skjolber.ndef.wellknown.handover.ErrorRecord;
import com.github.skjolber.ndef.wellknown.handover.HandoverCarrierRecord;
import com.github.skjolber.ndef.wellknown.handover.HandoverRequestRecord;
import com.github.skjolber.ndef.wellknown.handover.HandoverSelectRecord;

import android.annotation.SuppressLint;
import android.nfc.FormatException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;

/**
 * 
 * High-level representation of a {@link NdefRecord}
 * 
 * @author Thomas Rorvik Skjolberg ([email protected])
 *
 */

public abstract class Record {

	protected byte[] EMPTY = new byte[]{};

    private static final byte FLAG_MB = (byte) 0x80;
    private static final byte FLAG_ME = (byte) 0x40;
    private static final byte FLAG_SR = (byte) 0x10;
    private static final byte FLAG_IL = (byte) 0x08;

	/**
	 * 
	 * Modify a sequence of records so that first message has message begin flag and the last message has message end flag.
     *
     * @param ndefMessage message to modify
     */
	
    protected static void normalizeMessageBeginEnd(byte[] ndefMessage) {
    	normalizeMessageBeginEnd(ndefMessage, 0, ndefMessage.length);
    }

	/**
	 * 
	 * Modify a sequence of records so that first message has message begin flag and the last message has message end flag.
	 * 
     * @param ndefMessage message to modify
     * @param offset start offset
     * @param length number of bytes
     */
		
    protected static void normalizeMessageBeginEnd(byte[] ndefMessage, int offset, int length) {
    	// normalize message begin and message end messages

    	int count = offset;
    	while(count < offset + length) {
    		int headerCount = count;
    		int header = (ndefMessage[count++] & 0xff);
    		if (count >= offset + length) {
    			return; // invalid, defer error to NdefMessage parsing
    		}

    		int typeLength = (ndefMessage[count++] & 0xff);
    		if (count >= offset + length) {
    			return;  // invalid, defer error to NdefMessage parsing
    		}

    		int payloadLength;
    		if((header & FLAG_SR) != 0) {
    			payloadLength = (ndefMessage[count++] & 0xff);
    			if (count >= offset + length) {
    				return;  // invalid, defer error to NdefMessage parsing
    			}
    		} else {
    			if (count + 4 >= offset + length) {
    				return;  // invalid, defer error to NdefMessage parsing
    			}
    			payloadLength = (((ndefMessage[count] & 0xff) << 24) + ((ndefMessage[count + 1]  & 0xff) << 16) + ((ndefMessage[count + 2]  & 0xff) << 8) + ((ndefMessage[count+3]  & 0xff) << 0)); // strictly speaking this is a unsigned int

    			count += 4;
    		}

    		if((header & FLAG_IL) != 0) {
        		count += typeLength + payloadLength + (ndefMessage[count++] & 0xff);
    		} else {
        		count += typeLength + payloadLength;
    		}

    		// repair mb and me
    		if(headerCount == offset) {
    			// mb
    			header = header | FLAG_MB;
    		} else {
    			header = header & ~FLAG_MB;
    		}

    		if(count == offset + length) {
    			// me
    			header = header | FLAG_ME;
    		} else {
    			header = header & ~FLAG_ME;
    		}

			ndefMessage[headerCount] = (byte)header;
    	}
    }
    
    /**
     * Parse a byte-based {@link NdefRecord} into a high-level {@link Record}.
     * 
     * @param ndefRecord record to parse
     * @return corresponding {@link Record} subclass - {@link UnsupportedRecord} is not known.
     * @throws FormatException if known record type cannot be parsed
     */

	public static Record parse(NdefRecord ndefRecord) throws FormatException {
		short tnf = ndefRecord.getTnf();
		
		Record record = null;
		switch (tnf) {
        case NdefRecord.TNF_EMPTY: {
        	record = EmptyRecord.parse(ndefRecord);
        	
        	break;
        }
        case NdefRecord.TNF_WELL_KNOWN: {
        	record = parseWellKnown(ndefRecord);
        	
        	break;
        }
        case NdefRecord.TNF_MIME_MEDIA: {
        	record = MimeRecord.parse(ndefRecord);
        	
        	break;
        }
        case NdefRecord.TNF_ABSOLUTE_URI: {
        	record = AbsoluteUriRecord.parse(ndefRecord);
        	
        	break;
        }
        case NdefRecord.TNF_EXTERNAL_TYPE: {
        	record = ExternalTypeRecord.parse(ndefRecord);

        	break;
        }
        case NdefRecord.TNF_UNKNOWN: {
        	record = UnknownRecord.parse(ndefRecord);
        	
        	break;
        }
        /*
        case NdefRecord.TNF_UNCHANGED: {
        	throw new IllegalArgumentException("Chunked records no supported"); // chunks are abstracted away by android so should never happen
        }
        */
        	
		}

		if(record == null) { // pass through
			record = UnsupportedRecord.parse(ndefRecord);
		}
		
		if(ndefRecord.getId().length > 0) {
			record.setId(ndefRecord.getId());
		}
		
		return record;
	}
	
	/**
     * Parse a well-known byte-based {@link NdefRecord} into a well-known high-level {@link Record}.
     * 
     * @param ndefRecord record to parse
     * @return corresponding {@link Record} subclass - or null if not known
     * @throws FormatException if known record type cannot be parsed
     */
	
	protected static Record parseWellKnown(NdefRecord ndefRecord) throws FormatException {
		
        // lame type search among supported types
        byte[] type = ndefRecord.getType();
        if(type.length == 1) { 
        	// uri = U
        	// text = T
        	// gctarget = t
        	// gcdata = d
        	// gcaction a
        	switch(type[0]) {
        	case 'U' : {
        		return UriRecord.parseNdefRecord(ndefRecord);
        	}
        	case 'T' : {
        		
        		return TextRecord.parseNdefRecord(ndefRecord);
        	}
        	case 't' : {
        		
        		return GcTargetRecord.parseNdefRecord(ndefRecord);
        	}
        	case 'd' : {
        		
        		return GcDataRecord.parseNdefRecord(ndefRecord);
        	}
        	case 'a' : {
        		
        		return GcActionRecord.parseNdefRecord(ndefRecord);
        	}
        	}
        	
        } else if(type.length == 2) {

        	// smartposter = Sp
        	// genericcontrol = Gc
        	// alternativecarrier = ac
        	// handovercarrier = Hc
        	// handoverselect = Hs
        	// handoverrequest = Hr
        	// collision resolution = cr

        	switch(type[0]) {
        	case 'S' : {
        		if(type[1] == 'p') {
        			return SmartPosterRecord.parseNdefRecord(ndefRecord);
        		}
        		break;
        	}
        	case 'G' : {
        		if(type[1] == 'c') {
        			return GenericControlRecord.parseNdefRecord(ndefRecord);
        		}
        		break;
        	}
        	case 'a' : {
        		
        		if(type[1] == 'c') {
        			return AlternativeCarrierRecord.parseNdefRecord(ndefRecord);
        		}
        		break;
        	}
        	case 'c' : {
        		
        		if(type[1] == 'r') {
        			return CollisionResolutionRecord.parseNdefRecord(ndefRecord);
        		}
        		break;
        	}
        	case 'H' : {
        		if(type[1] == 'c') {
        			return HandoverCarrierRecord.parseNdefRecord(ndefRecord);
        		} else if(type[1] == 's') {
            		return HandoverSelectRecord.parseNdefRecord(ndefRecord);
        		} else if(type[1] == 'r') {
            		return HandoverRequestRecord.parseNdefRecord(ndefRecord);
            	}
        		break;
        	}
        	}
        	
        } else if(type.length == 3) {
        	// action = act
        	// error = err
        	// signature = Sig
        	if(type[0] == 'a' && type[1] == 'c' && type[2] == 't') {
        		return ActionRecord.parseNdefRecord(ndefRecord);
        	} else if(type[0] == 'e' && type[1] == 'r' && type[2] == 'r') {
        		return ErrorRecord.parseNdefRecord(ndefRecord);
        	} else if(type[0] == 'S' && type[1] == 'i' && type[2] == 'g') {
        		return SignatureRecord.parseNdefRecord(ndefRecord);
        	} 
        }
        
        return null;
        
	}
	
	/**
	 * Parse single record.
	 * 
     * @param record record to parse
     * @return corresponding {@link Record} subclass - or null if not known
     * @throws FormatException if known record type cannot be parsed
	 * @throws IllegalArgumentException if zero or more than one record
	 */
	
	protected static Record parse(byte[] record) throws FormatException {
		NdefMessage message = new NdefMessage(record);
		if(message.getRecords().length != 1) {
			throw new IllegalArgumentException("Single record expected");
		}
		return Record.parse(message.getRecords()[0]);
	}
	
	/**
	 * Parse single record.
	 * 
     * @param record record to parse
     * @param offset start offset
     * @param length number of bytes
     * @return corresponding {@link Record} subclass - or null if not known
     * @throws FormatException if known record type cannot be parsed
	 * @throws IllegalArgumentException if zero or more than one record
	 */

	
	protected static Record parse(byte[] record, int offset, int length) throws FormatException {
		byte[] recordsPayload = new byte[length];
		System.arraycopy(record, offset, recordsPayload, 0, length);
		
		return parse(recordsPayload);
	}	
	
	protected byte[] id = null;

	/**
	 * Get the record id
	 * 
	 * @return record id bytes
	 */
	
	public byte[] getId() {
		return id;
	}
	
	/**
	 * Set the record id
	 * 
	 * @param id id as bytes
	 */

	public void setId(byte[] id) {
		this.id = id;
	}

	/**
	 * 
	 * Set record id using string
	 * 
	 * @param key key as string
	 */
	
	public void setKey(String key) {
		this.id = key.getBytes();
	}
	
	/**
	 * 
	 * Get record id as string
	 * 
	 * @return id as string, or null
	 */

	public String getKey() {
		return id == null ? null : new String(id);
	}

	/**
	 * Check whether record id is set.
	 * 
	 * @return true if id is not null and length is over 0
	 */

	public boolean hasKey() {
		return id != null && id.length > 0;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + Arrays.hashCode(id);
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Record other = (Record)obj;
		if (!Arrays.equals(id, other.id))
			return false;
		return true;
	}

	/**
	 * Convert record to its byte-based {@link NdefRecord} representation.
	 * 
	 * @return record in {@link NdefRecord} form.
	 */
	
	public abstract NdefRecord getNdefRecord();

	/**
	 * Convert record to bytes. 
	 * 
	 * @return record in byte form as it was an {@link NdefMessage} with a single record.
	 */
	@SuppressLint("NewApi")
	public byte[] toByteArray() {
		if (android.os.Build.VERSION.SDK_INT >= 16) {
			return new NdefMessage(getNdefRecord()).toByteArray();
		} else {
			return new NdefMessage(new NdefRecord[]{getNdefRecord()}).toByteArray();
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy