summaryrefslogtreecommitdiff
path: root/SDL_Android/SmartDeviceLinkProxyAndroid/src/com/smartdevicelink/protocol/SmartDeviceLinkProtocol.java
blob: 286debed834fcf7951205a802a3825c7cce4284d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
package com.smartdevicelink.protocol;

import java.io.ByteArrayOutputStream;
import java.util.Hashtable;

import android.util.Log;

import com.smartdevicelink.exception.*;
import com.smartdevicelink.protocol.enums.*;
import com.smartdevicelink.util.BitConverter;
import com.smartdevicelink.util.DebugTool;

public class SmartDeviceLinkProtocol extends AbstractProtocol {
	byte _version = 1;
	private final static String FailurePropagating_Msg = "Failure propagating ";

	private static final int MTU_SIZE = 1500;
	private static int HEADER_SIZE = 8;
	private static int MAX_DATA_SIZE = MTU_SIZE - HEADER_SIZE;

	boolean _haveHeader = false;
	byte[] _headerBuf = new byte[HEADER_SIZE];
	int _headerBufWritePos = 0;
	ProtocolFrameHeader _currentHeader = null;
	byte[] _dataBuf = null;
	int _dataBufWritePos = 0;
	
	int hashID = 0;
	int messageID = 0;
	
	Hashtable<Integer, MessageFrameAssembler> _assemblerForMessageID = new Hashtable<Integer, MessageFrameAssembler>();
	Hashtable<Byte, Hashtable<Integer, MessageFrameAssembler>> _assemblerForSessionID = new Hashtable<Byte, Hashtable<Integer, MessageFrameAssembler>>();
	Hashtable<Byte, Object> _messageLocks = new Hashtable<Byte, Object>();

	// Hide no-arg ctor
	private SmartDeviceLinkProtocol() {
		super(null);
	} // end-ctor

	public SmartDeviceLinkProtocol(IProtocolListener protocolListener) {
		super(protocolListener);
	} // end-ctor
	
	public byte getVersion() {
		return this._version;
	}
	
	public void setVersion(byte version) {
		this._version = version;
		if (version == 2) {
			HEADER_SIZE = 12;
			MAX_DATA_SIZE = MTU_SIZE - HEADER_SIZE;
			_headerBuf = new byte[HEADER_SIZE];
		}
	}

	public void StartProtocolSession(SessionType sessionType) {
		ProtocolFrameHeader header = ProtocolFrameHeaderFactory.createStartSession(sessionType, 0x00, _version);
		sendFrameToTransport(header);
	} // end-method

	private void sendStartProtocolSessionACK(SessionType sessionType, byte sessionID) {
		ProtocolFrameHeader header = ProtocolFrameHeaderFactory.createStartSessionACK(sessionType, sessionID, 0x00, _version);
		sendFrameToTransport(header);
	} // end-method
	
	public void EndProtocolSession(SessionType sessionType, byte sessionID) {
		ProtocolFrameHeader header = ProtocolFrameHeaderFactory.createEndSession(sessionType, sessionID, hashID, _version);
		//byte[] data = new byte[4];
		//data = BitConverter.intToByteArray(hashID);
		//handleProtocolFrameToSend(header, data, 0, data.length);
		sendFrameToTransport(header);
	} // end-method

	public void SendMessage(ProtocolMessage protocolMsg) {	
		protocolMsg.setRPCType((byte) 0x00); //always sending a request
		SessionType sessionType = protocolMsg.getSessionType();
		byte sessionID = protocolMsg.getSessionID();
		
		byte[] data = null;
		if (_version == 2) {
			if (protocolMsg.getBulkData() != null) {
				data = new byte[12 + protocolMsg.getJsonSize() + protocolMsg.getBulkData().length];
				sessionType = SessionType.Bulk_Data;
			} else data = new byte[12 + protocolMsg.getJsonSize()];
			BinaryFrameHeader binFrameHeader = new BinaryFrameHeader();
			binFrameHeader = ProtocolFrameHeaderFactory.createBinaryFrameHeader(protocolMsg.getRPCType(), protocolMsg.getFunctionID(), protocolMsg.getCorrID(), protocolMsg.getJsonSize());
			System.arraycopy(binFrameHeader.assembleHeaderBytes(), 0, data, 0, 12);
			System.arraycopy(protocolMsg.getData(), 0, data, 12, protocolMsg.getJsonSize());
			if (protocolMsg.getBulkData() != null) {
				System.arraycopy(protocolMsg.getBulkData(), 0, data, 12 + protocolMsg.getJsonSize(), protocolMsg.getBulkData().length);
			}
		} else {
			data = protocolMsg.getData();
		}
		
		// Get the message lock for this protocol session
		Object messageLock = _messageLocks.get(sessionID);
		if (messageLock == null) {
			handleProtocolError("Error sending protocol message to SMARTDEVICELINK.", 
					new SmartDeviceLinkException("Attempt to send protocol message prior to startSession ACK.", SmartDeviceLinkExceptionCause.SMARTDEVICELINK_UNAVAILALBE));
			return;
		}
		
		synchronized(messageLock) {
			if (data.length > MAX_DATA_SIZE) {
				
				messageID++;
				ProtocolFrameHeader firstHeader = ProtocolFrameHeaderFactory.createMultiSendDataFirst(sessionType, sessionID, messageID, _version);
	
				// Assemble first frame.
				int frameCount = data.length / MAX_DATA_SIZE;
				if (data.length % MAX_DATA_SIZE > 0) {
					frameCount++;
				}
				//byte[] firstFrameData = new byte[HEADER_SIZE];
				byte[] firstFrameData = new byte[8];
				// First four bytes are data size.
				System.arraycopy(BitConverter.intToByteArray(data.length), 0, firstFrameData, 0, 4);
				// Second four bytes are frame count.
				System.arraycopy(BitConverter.intToByteArray(frameCount), 0, firstFrameData, 4, 4);
				
				handleProtocolFrameToSend(firstHeader, firstFrameData, 0, firstFrameData.length);
				
				int currentOffset = 0;
				byte frameSequenceNumber = 0;
				
				for (int i = 0; i < frameCount; i++) {
					if (i < (frameCount - 1)) {
						frameSequenceNumber = (byte)(i + 1);
					} else {
						frameSequenceNumber = ProtocolFrameHeader.FrameDataFinalConsecutiveFrame;
					} // end-if
					
					int bytesToWrite = data.length - currentOffset;
					if (bytesToWrite > MAX_DATA_SIZE) { 
						bytesToWrite = MAX_DATA_SIZE; 
					}

					ProtocolFrameHeader consecHeader = ProtocolFrameHeaderFactory.createMultiSendDataRest(sessionType, sessionID, bytesToWrite, frameSequenceNumber , messageID, _version);
					handleProtocolFrameToSend(consecHeader, data, currentOffset, bytesToWrite);
					currentOffset += bytesToWrite;
				}
			} else {
				messageID++;
				ProtocolFrameHeader header = ProtocolFrameHeaderFactory.createSingleSendData(sessionType, sessionID, data.length, messageID, _version);
				handleProtocolFrameToSend(header, data, 0, data.length);
			}
		}
	}

	private void sendFrameToTransport(ProtocolFrameHeader header) {
		handleProtocolFrameToSend(header, null, 0, 0);
	}

	public void HandleReceivedBytes(byte[] receivedBytes, int receivedBytesLength) {
		int receivedBytesReadPos = 0;
		
		//Check for a version difference
		if (_version == 1) {
			//Nothing has been read into the buffer and version is 2
			if (_headerBufWritePos == 0 && (byte) (receivedBytes[0] >>> 4) == 2) {
				setVersion((byte) (receivedBytes[0] >>> 4));
			//Buffer has something in it and version is 2
			} else if ((byte) (_headerBuf[0] >>> 4) == 2) {
				//safe current state of the buffer and also set the new version
				byte[] tempHeader = new byte[_headerBufWritePos];
				tempHeader = _headerBuf;
				setVersion((byte) (_headerBuf[0] >>> 4));
				_headerBuf = tempHeader;
			}
		}
		
		// If I don't yet know the message size, grab those bytes.
		if (!_haveHeader) {
			// If I can't get the size, just get the bytes that are there.
			int headerBytesNeeded = _headerBuf.length - _headerBufWritePos;
			if (receivedBytesLength < headerBytesNeeded) {
				System.arraycopy(receivedBytes, receivedBytesReadPos,
						_headerBuf, _headerBufWritePos, receivedBytesLength);
				_headerBufWritePos += receivedBytesLength;
				return;
			} else {
			// If I got the size, allocate the buffer
				System.arraycopy(receivedBytes, receivedBytesReadPos,
						_headerBuf, _headerBufWritePos, headerBytesNeeded);
				_headerBufWritePos += headerBytesNeeded;
				receivedBytesReadPos += headerBytesNeeded;
				_haveHeader = true;
				_currentHeader  = ProtocolFrameHeader.parseSmartDeviceLinkProHeader(_headerBuf);
				
				
				int iDataSize = _currentHeader.getDataSize();	

				if (iDataSize <= 4000)
				{
					_dataBuf = new byte[iDataSize];
				}
				else
				{
					//something is wrong with the header
					Log.e("HandleReceivedBytes", "Corrupt header found, request to allocate a byte array of size: " + iDataSize);	
					Log.e("HandleReceivedBytes", "_headerBuf: " + _headerBuf.toString());
					Log.e("HandleReceivedBytes", "_currentHeader: " + _currentHeader.toString());
					Log.e("HandleReceivedBytes", "receivedBytes: " + receivedBytes.toString());
					Log.e("HandleReceivedBytes", "receivedBytesReadPos: " + receivedBytesReadPos);
					Log.e("HandleReceivedBytes", "_headerBufWritePos: " + _headerBufWritePos);
					Log.e("HandleReceivedBytes", "headerBytesNeeded: " + headerBytesNeeded);
					handleProtocolError("Error handling protocol message from SMARTDEVICELINK, header invalid.", 
							new SmartDeviceLinkException("Error handling protocol message from SMARTDEVICELINK, header invalid.", SmartDeviceLinkExceptionCause.INVALID_HEADER));
					return;					
				}
				_dataBufWritePos = 0;
			}
		}

		int bytesLeft = receivedBytesLength - receivedBytesReadPos;
		int bytesNeeded = _dataBuf.length - _dataBufWritePos;
		// If I don't have enough bytes for the message, just grab what's there.
		if (bytesLeft < bytesNeeded) {
			System.arraycopy(receivedBytes, receivedBytesReadPos, _dataBuf,
					_dataBufWritePos, bytesLeft);
			_dataBufWritePos += bytesLeft;
			return;
		} else {
		// Fill the buffer and call the handler!
			System.arraycopy(receivedBytes, receivedBytesReadPos, _dataBuf, _dataBufWritePos, bytesNeeded);
			receivedBytesReadPos += bytesNeeded;

			MessageFrameAssembler assembler = getFrameAssemblerForFrame(_currentHeader);
			handleProtocolFrameReceived(_currentHeader, _dataBuf, assembler);

			// Reset all class member variables for next frame
			_dataBuf = null;
			_dataBufWritePos = 0;
			_haveHeader = false;
			_headerBuf = new byte[HEADER_SIZE];
			_currentHeader = null;
			_headerBufWritePos = 0;
			
			// If there are any bytes left, recurse.
			int moreBytesLeft = receivedBytesLength - receivedBytesReadPos;
			if (moreBytesLeft > 0) {
				byte[] moreBytes = new byte[moreBytesLeft];
				System.arraycopy(receivedBytes, receivedBytesReadPos,
						moreBytes, 0, moreBytesLeft);
				HandleReceivedBytes(moreBytes, moreBytesLeft);
			}
		}
	}
	
	protected MessageFrameAssembler getFrameAssemblerForFrame(ProtocolFrameHeader header) {
		Hashtable<Integer, MessageFrameAssembler> hashSessionID = _assemblerForSessionID.get(new Byte(header.getSessionID()));
		if (hashSessionID == null) {
			hashSessionID = new Hashtable<Integer, MessageFrameAssembler>();
			_assemblerForSessionID.put(new Byte(header.getSessionID()), hashSessionID);
		} // end-if
		
		MessageFrameAssembler ret = (MessageFrameAssembler) _assemblerForMessageID.get(new Integer(header.getMessageID()));
		if (ret == null) {
			ret = new MessageFrameAssembler();
			_assemblerForMessageID.put(new Integer(header.getMessageID()), ret);
		} // end-if
		
		return ret;
	} // end-method

	protected class MessageFrameAssembler {
		protected boolean hasFirstFrame = false;
		protected boolean hasSecondFrame = false;
		protected ByteArrayOutputStream accumulator = null;
		protected int totalSize = 0;
		protected int framesRemaining = 0;

		protected void handleFirstDataFrame(ProtocolFrameHeader header, byte[] data) {
			//The message is new, so let's figure out how big it is.
			hasFirstFrame = true;
			totalSize = BitConverter.intFromByteArray(data, 0) - HEADER_SIZE;
			framesRemaining = BitConverter.intFromByteArray(data, 4);
			accumulator = new ByteArrayOutputStream(totalSize);
		}
		
		protected void handleSecondFrame(ProtocolFrameHeader header, byte[] data) {
			handleRemainingFrame(header, data);
		}
		
		protected void handleRemainingFrame(ProtocolFrameHeader header, byte[] data) {
			accumulator.write(data, 0, header.getDataSize());
			notifyIfFinished(header);
		}
		
		protected void notifyIfFinished(ProtocolFrameHeader header) {
			//if (framesRemaining == 0) {
			if (header.getFrameType() == FrameType.Consecutive && header.getFrameData() == 0x0) 
			{
				ProtocolMessage message = new ProtocolMessage();
				message.setSessionType(header.getSessionType());
				message.setSessionID(header.getSessionID());
				//If it is SmartDeviceLinkPro 2.0 it must have binary header
				if (_version == 2) {
					BinaryFrameHeader binFrameHeader = BinaryFrameHeader.
							parseBinaryHeader(accumulator.toByteArray());
					message.setVersion(_version);
					message.setRPCType(binFrameHeader.getRPCType());
					message.setFunctionID(binFrameHeader.getFunctionID());
					message.setCorrID(binFrameHeader.getCorrID());
					if (binFrameHeader.getJsonSize() > 0) message.setData(binFrameHeader.getJsonData());
					if (binFrameHeader.getBulkData() != null) message.setBulkData(binFrameHeader.getBulkData());
				} else message.setData(accumulator.toByteArray());
				
				_assemblerForMessageID.remove(header.getMessageID());
				
				try {
					handleProtocolMessageReceived(message);
				} catch (Exception excp) {
					DebugTool.logError(FailurePropagating_Msg + "onProtocolMessageReceived: " + excp.toString(), excp);
				} // end-catch
				
				hasFirstFrame = false;
				hasSecondFrame = false;
				accumulator = null;
			} // end-if
		} // end-method
		
		protected void handleMultiFrameMessageFrame(ProtocolFrameHeader header, byte[] data) {
			//if (!hasFirstFrame) {
			//	hasFirstFrame = true;
			if (header.getFrameType() == FrameType.First)
			{
				handleFirstDataFrame(header, data);
			}
				
			//} else if (!hasSecondFrame) {
			//	hasSecondFrame = true;
			//	framesRemaining--;
			//	handleSecondFrame(header, data);
			//} else {
			//	framesRemaining--;
			else
			{
				handleRemainingFrame(header, data);
			}
				
			//}
		} // end-method
		
		protected void handleFrame(ProtocolFrameHeader header, byte[] data) {
			if (header.getFrameType().equals(FrameType.Control)) {
				handleControlFrame(header, data);
			} else {
				// Must be a form of data frame (single, first, consecutive, etc.)
				if (   header.getFrameType() == FrameType.First
					|| header.getFrameType() == FrameType.Consecutive
					) {
					handleMultiFrameMessageFrame(header, data);
				} else {
					handleSingleFrameMessageFrame(header, data);
				}
			} // end-if
		} // end-method
		
		
		private void handleControlFrame(ProtocolFrameHeader header, byte[] data) {
			if (header.getFrameData() == FrameDataControlFrameType.StartSession.getValue()) {
				sendStartProtocolSessionACK(header.getSessionType(), header.getSessionID());
			} else if (header.getFrameData() == FrameDataControlFrameType.StartSessionACK.getValue()) {
				// Use this sessionID to create a message lock
				Object messageLock = _messageLocks.get(header.getSessionID());
				if (messageLock == null) {
					messageLock = new Object();
					_messageLocks.put(header.getSessionID(), messageLock);
				}
				//hashID = BitConverter.intFromByteArray(data, 0);
				if (_version == 2) hashID = header.getMessageID();
				handleProtocolSessionStarted(header.getSessionType(), header.getSessionID(), _version, "");				
			} else if (header.getFrameData() == FrameDataControlFrameType.StartSessionNACK.getValue()) {
				handleProtocolError("Got StartSessionNACK for protocol sessionID=" + header.getSessionID(), null);
			} else if (header.getFrameData() == FrameDataControlFrameType.EndSession.getValue()) {
				//if (hashID == BitConverter.intFromByteArray(data, 0)) 
				if (_version == 2) {
					if (hashID == header.getMessageID())
						handleProtocolSessionEnded(header.getSessionType(), header.getSessionID(), "");
				} else handleProtocolSessionEnded(header.getSessionType(), header.getSessionID(), "");
			}
		} // end-method
				
		private void handleSingleFrameMessageFrame(ProtocolFrameHeader header, byte[] data) {
			ProtocolMessage message = new ProtocolMessage();
			if (header.getSessionType() == SessionType.RPC) {
				message.setMessageType(MessageType.RPC);
			} else if (header.getSessionType() == SessionType.Bulk_Data) {
				message.setMessageType(MessageType.BULK);
			} // end-if
			message.setSessionType(header.getSessionType());
			message.setSessionID(header.getSessionID());
			//If it is SmartDeviceLinkPro 2.0 it must have binary header
			if (_version == 2) {
				BinaryFrameHeader binFrameHeader = BinaryFrameHeader.
						parseBinaryHeader(data);
				message.setVersion(_version);
				message.setRPCType(binFrameHeader.getRPCType());
				message.setFunctionID(binFrameHeader.getFunctionID());
				message.setCorrID(binFrameHeader.getCorrID());
				if (binFrameHeader.getJsonSize() > 0) message.setData(binFrameHeader.getJsonData());
				if (binFrameHeader.getBulkData() != null) message.setBulkData(binFrameHeader.getBulkData());
			} else message.setData(data);
			
			_assemblerForMessageID.remove(header.getMessageID());
			
			try {
				handleProtocolMessageReceived(message);
			} catch (Exception ex) {
				DebugTool.logError(FailurePropagating_Msg + "onProtocolMessageReceived: " + ex.toString(), ex);
				handleProtocolError(FailurePropagating_Msg + "onProtocolMessageReceived: ", ex);
			} // end-catch
		} // end-method
	} // end-class
} // end-class