1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.nfc.snep;
18
19import android.nfc.FormatException;
20import android.nfc.NdefMessage;
21import android.nfc.NdefRecord;
22import com.android.nfc.NfcService;
23import com.android.nfc.sneptest.DtaSnepClient;
24
25import java.io.ByteArrayOutputStream;
26import java.io.DataOutputStream;
27import java.io.IOException;
28import java.io.UnsupportedEncodingException;
29import java.nio.ByteBuffer;
30
31public final class SnepMessage {
32    public static final byte VERSION_MAJOR = (byte) 0x1;
33    public static final byte VERSION_MINOR = (byte) 0x0;
34    public static final byte VERSION = (0xF0 & (VERSION_MAJOR << 4)) | (0x0F & VERSION_MINOR);
35
36    public static final byte REQUEST_CONTINUE = (byte) 0x00;
37    public static final byte REQUEST_GET = (byte) 0x01;
38    public static final byte REQUEST_PUT = (byte) 0x02;
39    public static final byte REQUEST_RFU = (byte) 0x03;
40    public static final byte REQUEST_REJECT = (byte) 0x7F;
41
42    public static final byte RESPONSE_CONTINUE = (byte) 0x80;
43    public static final byte RESPONSE_SUCCESS = (byte) 0x81;
44    public static final byte RESPONSE_NOT_FOUND = (byte) 0xC0;
45    public static final byte RESPONSE_EXCESS_DATA = (byte) 0xC1;
46    public static final byte RESPONSE_BAD_REQUEST = (byte) 0xC2;
47    public static final byte RESPONSE_NOT_IMPLEMENTED = (byte) 0xE0;
48    public static final byte RESPONSE_UNSUPPORTED_VERSION = (byte) 0xE1;
49    public static final byte RESPONSE_REJECT = (byte) 0xFF;
50
51    private static final byte[] NDEF_SHORT_TEST_RECORD = new byte[]{(byte)0xD1,(byte)0x01,(byte)0x1E,(byte)0x54,(byte)0x02,(byte)0x6C,(byte)0x61, // NDEF Header
52            (byte)0x4C,(byte)0x6F,(byte)0x72,(byte)0x65,(byte)0x6D,(byte)0x20,(byte)0x69,(byte)0x70,(byte)0x73,(byte)0x75, // Payload
53            (byte)0x6D,(byte)0x20,(byte)0x64,(byte)0x6F,(byte)0x6C,(byte)0x6F,(byte)0x72,(byte)0x20,(byte)0x73,(byte)0x69,
54            (byte)0x74,(byte)0x20,(byte)0x61,(byte)0x6D,(byte)0x65,(byte)0x74,(byte)0x2E};
55
56    private static final byte[] NDEF_TEST_RECORD = new byte[]{(byte)0xC1,(byte)0x01,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x1E,(byte)0x54,(byte)0x02,(byte)0x6C,(byte)0x61, // NDEF Header
57            (byte)0x4C,(byte)0x6F,(byte)0x72,(byte)0x65,(byte)0x6D,(byte)0x20,(byte)0x69,(byte)0x70,(byte)0x73,(byte)0x75, // Payload
58            (byte)0x6D,(byte)0x20,(byte)0x64,(byte)0x6F,(byte)0x6C,(byte)0x6F,(byte)0x72,(byte)0x20,(byte)0x73,(byte)0x69,
59            (byte)0x74,(byte)0x20,(byte)0x61,(byte)0x6D,(byte)0x65,(byte)0x74,(byte)0x2E};
60
61    private static final int HEADER_LENGTH = 6;
62    public static final int MAL_IUT = 0x0400;
63    public static final int MAL = 0xFFFFFFFF;
64    private final byte mVersion;
65    private final byte mField;
66    private final int mLength;
67    private final int mAcceptableLength;
68    private final NdefMessage mNdefMessage;
69
70    public static SnepMessage getGetRequest(int acceptableLength, NdefMessage ndef) {
71        return new SnepMessage(VERSION, REQUEST_GET, 4 + ndef.toByteArray().length,
72                acceptableLength, ndef);
73    }
74
75    public static SnepMessage getPutRequest(NdefMessage ndef) {
76        return new SnepMessage(VERSION, REQUEST_PUT, ndef.toByteArray().length, 0, ndef);
77    }
78
79    public static SnepMessage getMessage(byte field) {
80        return new SnepMessage(VERSION, field, 0, 0, null);
81    }
82
83    public static SnepMessage getSuccessResponse(NdefMessage ndef) {
84        if (ndef == null) {
85            return new SnepMessage(VERSION, RESPONSE_SUCCESS, 0, 0, null);
86        } else {
87            return new SnepMessage(VERSION, RESPONSE_SUCCESS, ndef.toByteArray().length, 0, ndef);
88        }
89    }
90
91    public static SnepMessage fromByteArray(byte[] data) throws FormatException {
92        return new SnepMessage(data);
93    }
94
95    public static NdefMessage getLargeNdef() throws UnsupportedEncodingException {
96        String snepTestData2 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus at"
97                +" lorem nunc, ut venenatis quam. Etiam id dolor quam, at viverra dolor."
98                +" Phasellus eu lacus ligula, quis euismod erat. Sed feugiat, ligula at"
99                +" mollis aliquet, justo lacus condimentum eros, non tincidunt neque"
100                +" ipsum eu risus. Sed adipiscing dui euismod tellus ullamcorper ornare."
101                +" Phasellus mattis risus et lectus euismod eu fermentum sem cursus."
102                +" Phasellus tristique consectetur mauris eu porttitor. Sed lobortis"
103                +" porttitor orci.";
104        String lang = "la";
105        byte[] textBytes = snepTestData2.getBytes();
106        byte[] langBytes = lang.getBytes("US-ASCII");
107        int langLength = langBytes.length;
108        int textLength = textBytes.length;
109
110        byte[] payload = new byte[1 + langLength + textLength];
111        payload[0] = (byte) langLength;
112
113        System.arraycopy(langBytes, 0, payload, 1, langLength);
114        System.arraycopy(textBytes, 0, payload, 1 + langLength, textLength);
115
116        NdefRecord data2 = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], payload);
117        return new NdefMessage(new NdefRecord[]{data2});
118    }
119
120    public static NdefMessage getSmallNdef() throws UnsupportedEncodingException {
121        String snepTestData1 = "Lorem ipsum dolor sit amet.";
122        String lang = "la";
123        byte[] textBytes = snepTestData1.getBytes();
124        byte[] langBytes = lang.getBytes("US-ASCII");
125        int langLength = langBytes.length;
126        int textLength = textBytes.length;
127
128        byte[] payload = new byte[1 + langLength + textLength];
129        payload[0] = (byte) langLength;
130
131        System.arraycopy(langBytes, 0, payload, 1, langLength);
132        System.arraycopy(textBytes, 0, payload, 1 + langLength, textLength);
133
134        NdefRecord data1 = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], payload);
135        return new NdefMessage(new NdefRecord[]{data1});
136    }
137
138    private SnepMessage(byte[] data) throws FormatException {
139        ByteBuffer input = ByteBuffer.wrap(data);
140        int ndefOffset;
141        int ndefLength;
142
143        mVersion = input.get();
144        mField = input.get();
145        mLength = input.getInt();
146        if (mField == REQUEST_GET) {
147            mAcceptableLength = input.getInt();
148            ndefOffset = HEADER_LENGTH + 4;
149            ndefLength = mLength - 4;
150        } else {
151            mAcceptableLength = -1;
152            ndefOffset = HEADER_LENGTH;
153            ndefLength = mLength;
154        }
155
156        if (ndefLength > 0) {
157            byte[] bytes = new byte[ndefLength];
158            System.arraycopy(data, ndefOffset, bytes, 0, ndefLength);
159            mNdefMessage = new NdefMessage(bytes);
160        } else {
161            mNdefMessage = null;
162        }
163    }
164
165    SnepMessage(byte version, byte field, int length, int acceptableLength,
166            NdefMessage ndefMessage) {
167        mVersion = version;
168        mField = field;
169        mLength = length;
170        mAcceptableLength = acceptableLength;
171        mNdefMessage = ndefMessage;
172    }
173
174    public byte[] toByteArray() {
175        byte[] bytes;
176        if (mNdefMessage != null) {
177            if (NfcService.sIsDtaMode && DtaSnepClient.mTestCaseId != 0) {
178               if (DtaSnepClient.mTestCaseId == 5 || DtaSnepClient.mTestCaseId == 6) {
179                   bytes = mNdefMessage.toByteArray();
180               } else {
181                   if (NfcService.sIsShortRecordLayout) {
182                       bytes = NDEF_SHORT_TEST_RECORD;
183                   } else {
184                       bytes = NDEF_TEST_RECORD;
185                   }
186               }
187            } else {
188                bytes = mNdefMessage.toByteArray();
189            }
190        } else {
191            bytes = new byte[0];
192        }
193
194        ByteArrayOutputStream buffer;
195        try {
196            if (mField == REQUEST_GET) {
197                buffer = new ByteArrayOutputStream(bytes.length + HEADER_LENGTH + 4);
198            } else {
199                buffer = new ByteArrayOutputStream(bytes.length + HEADER_LENGTH);
200            }
201
202            DataOutputStream output = new DataOutputStream(buffer);
203            output.writeByte(mVersion);
204            output.writeByte(mField);
205            if (mField == REQUEST_GET) {
206                output.writeInt(bytes.length + 4);
207                output.writeInt(mAcceptableLength);
208            } else {
209                output.writeInt(bytes.length);
210            }
211            output.write(bytes);
212        } catch(IOException e) {
213            return null;
214        }
215
216        return buffer.toByteArray();
217    }
218
219    public NdefMessage getNdefMessage() {
220        return mNdefMessage;
221    }
222
223    public byte getField() {
224        return mField;
225    }
226
227    public byte getVersion() {
228        return mVersion;
229    }
230
231    public int getLength() {
232        return mLength;
233    }
234
235    public int getAcceptableLength() {
236        if (mField != REQUEST_GET) {
237            throw new UnsupportedOperationException(
238                    "Acceptable Length only available on get request messages.");
239        }
240        return mAcceptableLength;
241    }
242}
243