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 com.android.nfc.DeviceHost.LlcpSocket;
20
21import android.nfc.FormatException;
22import android.util.Log;
23
24import java.io.ByteArrayInputStream;
25import java.io.ByteArrayOutputStream;
26import java.io.DataInputStream;
27import java.io.IOException;
28import java.util.Arrays;
29
30public class SnepMessenger {
31    private static final String TAG = "SnepMessager";
32    private static final boolean DBG = false;
33    private static final int HEADER_LENGTH = 6;
34    final LlcpSocket mSocket;
35    final int mFragmentLength;
36    final boolean mIsClient;
37
38    public SnepMessenger(boolean isClient, LlcpSocket socket, int fragmentLength) {
39        mSocket = socket;
40        mFragmentLength = fragmentLength;
41        mIsClient = isClient;
42    }
43
44    public void sendMessage(SnepMessage msg) throws IOException {
45        byte[] buffer = msg.toByteArray();
46        byte remoteContinue;
47        if (mIsClient) {
48            remoteContinue = SnepMessage.RESPONSE_CONTINUE;
49        } else {
50            remoteContinue = SnepMessage.REQUEST_CONTINUE;
51        }
52        if (DBG) Log.d(TAG, "about to send a " + buffer.length + " byte message");
53
54        // Send first fragment
55        int length = Math.min(buffer.length, mFragmentLength);
56        byte[] tmpBuffer = Arrays.copyOfRange(buffer, 0, length);
57        if (DBG) Log.d(TAG, "about to send a " + length + " byte fragment");
58        mSocket.send(tmpBuffer);
59
60        if (length == buffer.length) {
61            return;
62        }
63
64        // Look for Continue or Reject from peer.
65        int offset = length;
66        byte[] responseBytes = new byte[HEADER_LENGTH];
67        mSocket.receive(responseBytes);
68        SnepMessage snepResponse;
69        try {
70            snepResponse = SnepMessage.fromByteArray(responseBytes);
71        } catch (FormatException e) {
72            throw new IOException("Invalid SNEP message", e);
73        }
74
75        if (DBG) Log.d(TAG, "Got response from first fragment: " + snepResponse.getField());
76        if (snepResponse.getField() != remoteContinue) {
77            throw new IOException("Invalid response from server (" +
78                    snepResponse.getField() + ")");
79        }
80
81        // Send remaining fragments.
82        while (offset < buffer.length) {
83            length = Math.min(buffer.length - offset, mFragmentLength);
84            tmpBuffer = Arrays.copyOfRange(buffer, offset, offset + length);
85            if (DBG) Log.d(TAG, "about to send a " + length + " byte fragment");
86            mSocket.send(tmpBuffer);
87            offset += length;
88        }
89    }
90
91    public SnepMessage getMessage() throws IOException, SnepException {
92        ByteArrayOutputStream buffer = new ByteArrayOutputStream(mFragmentLength);
93        byte[] partial = new byte[mFragmentLength];
94        int size;
95        int requestSize = 0;
96        int readSize = 0;
97        byte requestVersion = 0;
98        boolean doneReading = false;
99        byte fieldContinue;
100        byte fieldReject;
101        if (mIsClient) {
102            fieldContinue = SnepMessage.REQUEST_CONTINUE;
103            fieldReject = SnepMessage.REQUEST_REJECT;
104        } else {
105            fieldContinue = SnepMessage.RESPONSE_CONTINUE;
106            fieldReject = SnepMessage.RESPONSE_REJECT;
107        }
108
109        size = mSocket.receive(partial);
110        if (DBG) Log.d(TAG, "read " + size + " bytes");
111        if (size < 0) {
112            try {
113                mSocket.send(SnepMessage.getMessage(fieldReject).toByteArray());
114            } catch (IOException e) {
115                // Ignore
116            }
117            throw new IOException("Error reading SNEP message.");
118        } else if (size < HEADER_LENGTH) {
119            try {
120                mSocket.send(SnepMessage.getMessage(fieldReject).toByteArray());
121            } catch (IOException e) {
122                // Ignore
123            }
124            throw new IOException("Invalid fragment from sender.");
125        } else {
126            readSize = size - HEADER_LENGTH;
127            buffer.write(partial, 0, size);
128        }
129
130        DataInputStream dataIn = new DataInputStream(new ByteArrayInputStream(partial));
131        requestVersion = dataIn.readByte();
132        byte requestField = dataIn.readByte();
133        requestSize = dataIn.readInt();
134
135        if (DBG) Log.d(TAG, "read " + readSize + " of " + requestSize);
136
137        if (((requestVersion & 0xF0) >> 4) != SnepMessage.VERSION_MAJOR) {
138            // Invalid protocol version; treat message as complete.
139            return new SnepMessage(requestVersion, requestField, 0, 0, null);
140        }
141
142        if (requestSize > readSize) {
143            if (DBG) Log.d(TAG, "requesting continuation");
144            mSocket.send(SnepMessage.getMessage(fieldContinue).toByteArray());
145        } else {
146            doneReading = true;
147        }
148
149        // Remaining fragments
150        while (!doneReading) {
151            try {
152                size = mSocket.receive(partial);
153                if (DBG) Log.d(TAG, "read " + size + " bytes");
154                if (size < 0) {
155                    try {
156                        mSocket.send(SnepMessage.getMessage(fieldReject).toByteArray());
157                    } catch (IOException e) {
158                        // Ignore
159                    }
160                    throw new IOException();
161                } else {
162                    readSize += size;
163                    buffer.write(partial, 0, size);
164                    if (readSize == requestSize) {
165                        doneReading = true;
166                    }
167                }
168            } catch (IOException e) {
169                try {
170                    mSocket.send(SnepMessage.getMessage(fieldReject).toByteArray());
171                } catch (IOException e2) {
172                    // Ignore
173                }
174                throw e;
175            }
176        }
177
178        // Build NDEF message set from the stream
179        try {
180            return SnepMessage.fromByteArray(buffer.toByteArray());
181        } catch (FormatException e) {
182            Log.e(TAG, "Badly formatted NDEF message, ignoring", e);
183            throw new SnepException(e);
184        }
185    }
186
187    public void close() throws IOException {
188        mSocket.close();
189    }
190}
191