Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/synchrona/jred/IrOBEX.java


1   /*
2   **************************************************************************
3   ** $Header: /cvsroot/jred/jred/src/com/synchrona/jred/IrOBEX.java,v 1.1.1.1 2000/07/05 04:41:52 mpatters Exp $
4   **
5   ** Copyright (C) 2000 Synchrona, Inc. All rights reserved.
6   **
7   ** This file is part of JRed, a 100% Java implementation of the IrDA
8   ** infrared communications protocols.
9   **
10  ** This file may be distributed under the terms of the Synchrona Public
11  ** License as defined by Synchrona, Inc. and appearing in the file
12  ** LICENSE included in the packaging of this file. The Synchrona Public
13  ** License is based on the Q Public License as defined by Troll Tech AS
14  ** of Norway; it differs only in its use of the courts of Florida, USA
15  ** rather than those of Oslo, Norway.
16  **************************************************************************
17  */
18  package com.synchrona.jred;
19  
20  import com.synchrona.util.Log;
21  import java.io.ByteArrayInputStream;
22  import java.io.ByteArrayOutputStream;
23  import java.io.DataInputStream;
24  import java.io.File;
25  import java.io.FileOutputStream;
26  import java.io.IOException;
27  import java.util.Enumeration;
28  import java.util.Hashtable;
29  
30  /**
31   * Implements the Infrared Data Association (IrDA) Object Exchange (OBEX)
32   * protocol.
33   */
34  public class IrOBEX {
35    public static final byte IROBEX_FLAGS              = (byte) 0x00;
36    public static final byte IROBEX_VERSION            = (byte) 0x10;
37    public static final byte OPCODE_CONNECT_REQUEST    = (byte) 0x00;
38    public static final byte OPCODE_DISCONNECT_REQUEST = (byte) 0x01;
39    public static final byte OPCODE_PUT_REQUEST        = (byte) 0x02;
40    public static final int  STATE_READY               = 37;
41    public static final int  STATE_RECEIVING           = 51;
42  
43    private byte []               m_ayPacket      = null;
44    private boolean               m_bDone         = false;
45    private File                  m_inbox         = null;
46    private Log                   m_log           = null;
47    private int                   m_nPacketLength = 0;
48    private int                   m_nState        = STATE_READY;
49    private ByteArrayOutputStream m_outStream     = null;
50    private Hashtable             m_params        = new Hashtable();
51  
52    public IrOBEX(Log log) {
53      m_log = log;
54  
55      // We hope the user defined an inbox directory. If not, we'll
56      // try to use the current directory. If that doesn't work, we
57      // won't be able to save anything.
58      String strInbox = System.getProperty("IrOBEX.InboxDirectory");
59      if ( null == strInbox ) {
60        m_log.warn("IrOBEX", "IrOBEX.InboxDirectory was not defined, using ./inbox instead.");
61        strInbox = "." + File.separator + "inbox";
62      }
63  
64      m_inbox = new File(strInbox);
65  
66      boolean bUseCurrent = false;
67      if ( m_inbox.exists() ) {
68        if ( !m_inbox.isDirectory() ) {
69          m_log.error("IrOBEX", "IrOBEX.InboxDirectory (" + strInbox + ") exists but is not a directory...using current instead.");
70          bUseCurrent = true;
71        }
72      } else {
73        // If mkdir() fails (most likely because of permissions), we'll
74        // have to use the current directory.
75        m_log.warn("IrOBEX", "Creating a new inbox directory: \"" + strInbox + "\"");
76        bUseCurrent = !m_inbox.mkdir();
77        if ( bUseCurrent ) {
78          m_log.error("IrOBEX", "Could not create IrOBEX.InboxDirectory (" + strInbox + ")...using current instead.");
79        } else if ( !m_inbox.canWrite() ) {
80          m_log.error("IrOBEX", "Cannot write to  IrOBEX.InboxDirectory (" + strInbox + ")...using current instead.");
81        }
82      }
83  
84      if ( bUseCurrent ) {
85        m_inbox = new File(".");
86        if ( m_inbox.canWrite() ) {
87          // If we weren't able to write to the current directory,
88          // we just can't save anymore.
89          m_log.error("IrOBEX", "Cannot write to current directory. IrOBEX will not be able to save any beamed objects.");
90        }
91      }
92    }
93  
94    public void serviceRequest(TinyTP tinyTP, IrLMPConnection conn, byte [] ayData, int nOffset, int nLength) throws Exception {
95      if ( STATE_READY == m_nState ) {
96        m_outStream     = new ByteArrayOutputStream();
97        m_nPacketLength = (0x0000FF00 & (ayData[nOffset + 1] << 8)) | (0x000000FF & ayData[nOffset + 2]);
98        m_log.debug("IrOBEX", "(serviceRequest) starting new packet: opcode=" + ayData[nOffset] + " length=" + m_nPacketLength);
99      } else {
100       m_log.debug("IrOBEX", "(serviceRequest) appending to current packet");
101     }
102 
103     m_outStream.write(ayData, nOffset, nLength);
104     m_bDone = (m_nPacketLength == m_outStream.size()); 
105 
106     if ( m_bDone ) {
107       m_log.debug("IrOBEX", "(serviceRequest) packet complete " + m_outStream.size());
108       m_nState   = STATE_READY;
109       m_ayPacket = m_outStream.toByteArray();
110       processPacket(tinyTP);
111     } else {
112       m_nState = STATE_RECEIVING;
113     }
114   }
115 
116   /**
117    * Save a beamed object as a file in the inbox directory.
118    */
119   private void doPut() {
120     m_log.debug("IrOBEX", "(doPut)");
121 
122     String strName = (String) m_params.get("Name");
123     m_log.debug("IrOBEX", "\tName: " + strName);
124 
125     String strDescription = (String) m_params.get("Description");
126     m_log.debug("IrOBEX", "\tDescription: " + strDescription);
127 
128     ByteArrayOutputStream body = (ByteArrayOutputStream) m_params.get("Body");
129     m_log.debug("IrOBEX", "\tBody: " + body.toString());
130 
131     //
132     // Object names can contain characters with special meanings to the 
133     // receiver. For instance, Palm application names can have '/' characters
134     // in them, which Unix platforms use to denote directories. We need to
135     // replace path separators with something, and here we use '_' characters.
136     //
137     String newName      = strName.replace(File.separatorChar, '_');
138     File   beamedObject = new File(m_inbox.getName() + File.separator + newName);
139     
140     m_log.debug("IrOBEX", "\tsaving to " + beamedObject.getName());
141 
142     try {
143       FileOutputStream out = new FileOutputStream(beamedObject);
144       body.writeTo(out);
145       out.close();
146     } catch ( IOException ioException ) {
147       m_log.error("IrOBEX", ioException.getMessage());
148     }
149   }
150 
151   private void processPacket(TinyTP tinyTP) {
152     m_log.debug("IrOBEX", "(processPacket)");
153 
154     boolean bFinal  = (0 != (m_ayPacket[0] & 0x80));
155     byte    yOpcode = (byte) (m_ayPacket[0] & 0x7F);
156 
157     switch ( yOpcode ) {
158       case OPCODE_CONNECT_REQUEST:
159         handleConnectRequest(tinyTP, bFinal);
160       break;
161 
162       case OPCODE_DISCONNECT_REQUEST:
163         handleDisconnectRequest(tinyTP, bFinal);
164       break;
165 
166       case OPCODE_PUT_REQUEST:
167         handlePutRequest(tinyTP, bFinal);
168       break;
169 
170       default:
171         m_log.warn("IrOBEX", "Unimplemented opcode: " + yOpcode);
172       break;
173     }
174   }
175 
176   private void handleConnectRequest(TinyTP tinyTP, boolean bFinal) {
177     m_log.debug("IrOBEX", "Connect-Request");
178 
179     int  nPacketLength    = (m_ayPacket[1] << 8) | m_ayPacket[2];
180     byte yVersion         = m_ayPacket[3];
181     byte yFlags           = m_ayPacket[4];
182     int  nMaxPacketLength = (m_ayPacket[5] << 8) | m_ayPacket[6];
183 
184     byte [] ayResult = new byte[255];
185     int     nLength  = 0;
186 
187     ayResult[nLength++] = (byte) 0xA0;           // 0: success
188     nLength += 2;                                // 1 & 2: leave space for packet length
189     ayResult[nLength++] = (byte) IROBEX_VERSION; // 3: IrOBEX version
190     ayResult[nLength++] = (byte) IROBEX_FLAGS;   // 4: flags
191 
192     // bytes 5 & 6: maximum packet length
193     ayResult[nLength++] = (byte) ((nMaxPacketLength & 0x0000FF00) >> 8);
194     ayResult[nLength++] = (byte)  (nMaxPacketLength & 0x000000FF); 
195 
196     ayResult[1]         = (byte) ((nLength & 0x0000FF00) >> 8);
197     ayResult[2]         = (byte) (nLength & 0x0000000FF);
198 
199     tinyTP.dataRequest(ayResult, 0, nLength);
200   }
201 
202   private void handleDisconnectRequest(TinyTP tinyTP, boolean bFinal) {
203     m_log.debug("IrOBEX", "Disconnect-Request");
204 
205     byte [] ayResponse      = new byte[3];
206     int     nResponseLength = 0;
207 
208     ayResponse[nResponseLength++] = (byte) 0xA0;
209     ayResponse[nResponseLength++] = (byte) 0x00;
210     ayResponse[nResponseLength++] = (byte) 0x03;
211   
212     tinyTP.dataRequest(ayResponse, 0, nResponseLength);
213   }
214 
215   private void handlePutRequest(TinyTP tinyTP, boolean bFinal) {
216     m_log.debug("IrOBEX", "Put-Request packet-length=" + m_nPacketLength);
217 
218     try {
219       parseHeaders();
220       if ( bFinal ) {
221         doPut();
222         m_params.clear();
223       }
224     } catch ( Exception e ) {
225       m_log.error("IrOBEX", e.toString());
226     }
227 
228     byte [] ayResponse      = new byte[3];
229     int     nResponseLength = 0;
230 
231     ayResponse[nResponseLength++] = (byte) (bFinal ? 0xA0 : 0x90);
232     ayResponse[nResponseLength++] = (byte) 0x00;
233     ayResponse[nResponseLength++] = (byte) 0x03;
234   
235     tinyTP.dataRequest(ayResponse, 0, nResponseLength);
236   }
237 
238   private void parseHeaders() throws Exception {
239     int             nLength = 0;
240     DataInputStream data    = new DataInputStream(new ByteArrayInputStream(m_ayPacket));
241 
242     // Packets begin with 1-byte opcode, but we already know it's
243     // a PUT request (although we should assert that here)
244     data.readByte();
245 
246     // Next is a 16-bit packet length
247     short nPacketLength = data.readShort();
248     m_log.debug("IrOBEX", "(parseHeaders) nPacketLength: " + nPacketLength);
249 
250     while ( data.available() > 0 ) {
251       // Figure out datatype
252       String  strName = null;
253       Object  value   = null;
254       byte [] ayBody  = null;
255       boolean bBody   = false; // reinitialize each time through
256 
257       // Each header begins with a 1-byte type.
258       int yHeader = data.readByte();
259       m_log.debug("IrOBEX", "yHeader=" + yHeader);
260 
261       switch ( (byte) (0x000000FF & yHeader) ) {
262         case (byte) 0xC0: strName = "Count";           break;
263         case (byte) 0x01: strName = "Name";            break;
264         case (byte) 0x42: strName = "Type";            break;
265         case (byte) 0xC3: strName = "Length";          break;
266         case (byte) 0x44: strName = "Time";            break;
267         case (byte) 0x05: strName = "Description";     break;
268         case (byte) 0x46: strName = "Target";          break;
269         case (byte) 0x47: strName = "HTTP";            break;
270 
271         // See below for special handling of Body & End-Of-Body
272         // chunks.
273         case (byte) 0x48: // fall through
274         case (byte) 0x49:
275           strName = "Body";
276           bBody   = true;
277         break;
278 
279         case (byte) 0x4A: strName = "Who";             break;
280         case (byte) 0xCB: strName = "Connection-ID";   break;
281         case (byte) 0x4C: strName = "App-Parameters";  break;
282         case (byte) 0x4D: strName = "Challenge";       break;
283         case (byte) 0x4E: strName = "Response";        break;
284         case (byte) 0x4F: strName = "Object-Class";    break;
285 
286         // Not part of the IrOBEX specification, and outside
287         // the range of ID's that are supposed to be recognized
288         // by IrOBEX services. It's a 4-byte ASCII string disguised
289         // as an int. Is this considered intellectual property
290         // of Palm Computing? If so, we need to ignore it.
291         case (byte) 0xCF: strName = "Palm-Creator-ID"; break;
292 
293         default:
294           strName = "Unknown (" + yHeader + ")";
295         break;
296       }
297 
298       m_log.debug("IrOBEX", strName);
299 
300       switch ( 0x000000C0 & yHeader ) {
301         case 0x00000000:
302           // Unicode text, null terminated, prefixed by 2-byte length
303           // Length includes header byte and 2-byte length, so we need
304           // to subtract 3 before continuing
305           nLength = data.readShort() - 3;
306           m_log.debug("IrOBEX", "reading string length=" + nLength);
307           // Java uses modified UTF-8, while OBEX specifies
308           // UTF-16. We can convert by reading 16-bit characters
309           // and constructing a string from them.
310           char [] acText = new char[nLength / 2];
311           for ( int i = 0; i < acText.length; i++ ) {
312             acText[i] = data.readChar();
313           }
314           // Convert to string, minus the trailing null
315           String strValue = new String(acText, 0, acText.length - 1);
316           m_log.debug("IrOBEX", "strValue: #"  + strValue + "#");
317           value = strValue;
318         break;
319 
320         case 0x00000040:
321           // Byte sequence prefaced by 2-byte length
322           // Length includes header byte and 2-byte length, so we need
323           // to subtract 3 before continuing
324           nLength = data.readShort() - 3;
325           m_log.debug("IrOBEX", "reading binary sequence length=" + nLength);
326           ayBody = new byte[nLength];
327           data.read(ayBody);
328           // If this sequence is a Body or End-Of-Body chunk,
329           // it will be contenated into the preceding body
330           // segments below.
331           String strBody = new String(ayBody);
332           m_log.debug("IrOBEX", "body: #" + strBody + "#");
333         break;
334 
335         case 0x00000080: // 1 byte
336           m_log.debug("IrOBEX", "reading byte");
337           value = new Byte(data.readByte());
338         break;
339 
340         case 0x000000C0: // 4-byte integer
341           m_log.debug("IrOBEX", "reading int");
342           value = new Integer(data.readInt());
343         break;
344 
345         default:
346           throw new Exception("Unhandled datatype: " + yHeader);
347       }
348 
349       m_log.debug("IrOBEX", "(parseHeaders) " + strName);
350 
351       // We need to handle Body and End-Of-Body chunks differently,
352       // because they need to be concatenated into a single stream
353       // of bytes.
354       if ( bBody ) {
355         m_log.debug("IrOBEX", "bBody was true");
356         if ( (null != ayBody) && (ayBody.length > 0 ) ) {
357           ByteArrayOutputStream body =
358             (ByteArrayOutputStream) m_params.get("Body");
359 
360           if ( null == body ) {
361             m_log.debug("IrOBEX", "allocating new ByteArrayOutputStream");
362             body = new ByteArrayOutputStream();
363           }
364 
365           body.write(ayBody, 0, ayBody.length);
366           m_log.debug("IrOBEX", "putting body into hash table");
367           m_params.put("Body", body);
368         }
369       } else {
370         m_log.debug("IrOBEX", "putting key [" + strName + "] value [" + value + "]");
371         m_params.put(strName, value);
372       }
373     }  
374   }
375 
376   private void connectRequest(TinyTP tinyTP) {
377     byte [] ayRequest      = new byte[255];
378     int     nRequestLength = 0;
379 
380     ayRequest[nRequestLength++] = (byte) 0x80;           // 0: opcode
381     nRequestLength += 2;                                 // 1 & 2: leave space for packet length
382     ayRequest[nRequestLength++] = (byte) IROBEX_VERSION; // 3: IrOBEX version
383     ayRequest[nRequestLength++] = (byte) IROBEX_FLAGS;   // 4: flags
384   
385     // bytes 5 & 6: maximum packet length
386     ayRequest[nRequestLength++] = (byte) 0xFF;
387     ayRequest[nRequestLength++] = (byte) 0xFF;
388   
389     ayRequest[1] = (byte) ((nRequestLength & 0x0000FF00) >> 8);
390     ayRequest[2] = (byte) (nRequestLength & 0x0000000FF);
391 
392     tinyTP.dataRequest(ayRequest, 0, nRequestLength);
393   }
394 }