OSDN Git Service

auto import from //depot/cupcake/@132589
[android-x86/packages-apps-IM.git] / src / com / android / im / imps / PtsPrimitiveParser.java
1 /*
2  * Copyright (C) 2007 Esmertec AG.
3  * Copyright (C) 2007 The Android Open Source Project
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 package com.android.im.imps;
19
20 import java.io.BufferedReader;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.InputStreamReader;
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.Map.Entry;
27 import java.util.regex.Matcher;
28 import java.util.regex.Pattern;
29
30 import com.android.im.imps.Primitive.TransactionMode;
31
32 /**
33  * PTS/SMS encoded IMPS messages parser. Only response transactions and
34  * server initiated requests are supported.
35  */
36 public class PtsPrimitiveParser implements PrimitiveParser {
37
38     // WVaaBBcccDD <parameters>
39     //   aa - version number; 12 for 1.2, 13 for 1.3; "XX" for version discovery
40     //   BB - message type, case insensitive
41     //   ccc - transaction id in range 0-999 without preceding zero
42     //   DD - multiple SMSes identifier
43     private static final Pattern sPreamplePattern =
44         Pattern.compile("\\AWV(\\d{2})(\\p{Alpha}{2})(\\d{1,3})(\\p{Alpha}{2})?(\\z| .*)");
45
46     private char mReadBuf[] = new char[256];
47     private StringBuilder mStringBuf = new StringBuilder();
48     private int mPos;
49
50     private static int UNCERTAIN_GROUP_SIZE = -1;
51
52     public Primitive parse(InputStream in) throws ParserException, IOException {
53         // assuming PTS data is always short
54         BufferedReader reader = new BufferedReader(
55                 new InputStreamReader(in, "UTF-8"), 128);
56         mStringBuf.setLength(0);
57         mPos = 0;
58         int len;
59         while ((len = reader.read(mReadBuf)) != -1) {
60             mStringBuf.append(mReadBuf, 0, len);
61         }
62         return parsePrim();
63     }
64
65     private Primitive parsePrim() throws ParserException
66     {
67         Matcher m = sPreamplePattern.matcher(mStringBuf);
68         if (!m.matches()) {
69             throw new ParserException("Invalid PTS encoded message");
70         }
71
72         Primitive p = new Primitive();
73
74         // TODO: handle WV version in m.group(1)
75
76         String type = m.group(2).toUpperCase();
77         String transactionType = PtsCodes.getTransaction(type);
78         if (transactionType == null) {
79             throw new ParserException("Unrecognized transaction code " + type);
80         }
81         p.setContentElement(transactionType);
82
83         if (PtsCodes.isServerRequestCode(type)) {
84             p.setTransactionMode(TransactionMode.Request);
85         } else {
86             p.setTransactionMode(TransactionMode.Response);
87         }
88
89         p.setTransactionId(m.group(3));
90         mPos = m.start(5);
91
92         if (mPos < mStringBuf.length()) {
93             match(' ');
94
95             HashMap<String, ParamValue> params = parseParams();
96             for (Entry<String, ParamValue> param : params.entrySet()) {
97                 translateParam(p, param.getKey(), param.getValue());
98             }
99         }
100         return p;
101     }
102
103     private static HashMap<String, Integer> sInfoElemTypeMap;
104     private static final int ELEM_OTHER_SIMPLE         = 0;
105     private static final int ELEM_SESSION_ID           = 1;
106     private static final int ELEM_RESULT               = 2;
107     private static final int ELEM_ALL_FUNCTIONS        = 3;
108     private static final int ELEM_NOT_AVAIL_FUNCS      = 4;
109     private static final int ELEM_CAPABILITY_LIST      = 5;
110     private static final int ELEM_CONTACT_LIST         = 6;
111     private static final int ELEM_DEFAULT_CONTACT_LIST = 7;
112     private static final int ELEM_USER_NICK_LIST       = 8;
113     private static final int ELEM_CONTACT_LIST_PROPS   = 9;
114     private static final int ELEM_PRESENCE             = 10;
115
116     /*
117     private static final int ELEM_RESULT_CLIST  = 3;
118     private static final int ELEM_RESULT_DOMAIN = 4;
119     private static final int ELEM_RESULT_GROUP  = 5;
120     private static final int ELEM_RESULT_MSGID  = 6;
121     private static final int ELEM_RESULT_SCRNAME = 7;
122     private static final int ELEM_RESULT_USER   = 8;
123     */
124
125     static {
126         sInfoElemTypeMap = new HashMap<String, Integer>();
127         sInfoElemTypeMap.put(PtsCodes.SessionID, ELEM_SESSION_ID);
128         sInfoElemTypeMap.put(PtsCodes.Status, ELEM_RESULT);
129         sInfoElemTypeMap.put(PtsCodes.NotAvailableFunctions, ELEM_NOT_AVAIL_FUNCS);
130         sInfoElemTypeMap.put(PtsCodes.AllFunctions, ELEM_ALL_FUNCTIONS);
131         sInfoElemTypeMap.put(PtsCodes.AgreedCapabilityList, ELEM_CAPABILITY_LIST);
132         sInfoElemTypeMap.put(PtsCodes.ContactList, ELEM_CONTACT_LIST);
133         sInfoElemTypeMap.put(PtsCodes.DefaultContactList, ELEM_DEFAULT_CONTACT_LIST);
134         sInfoElemTypeMap.put(PtsCodes.UserNickList, ELEM_USER_NICK_LIST);
135         sInfoElemTypeMap.put(PtsCodes.ContactListProps, ELEM_CONTACT_LIST_PROPS);
136         sInfoElemTypeMap.put(PtsCodes.Presence, ELEM_PRESENCE);
137     }
138
139     private static void translateParam(Primitive p, String elemCode,
140             ParamValue elemValue) throws ParserException {
141         int type;
142         elemCode = elemCode.toUpperCase();
143
144         // FIXME: Should be refactored when we had concrete situation of the null value case
145         if (elemValue == null) {
146             throw new ParserException("Parameter " + elemCode + " must have value.");
147         }
148
149         if (sInfoElemTypeMap.containsKey(elemCode)) {
150             type = sInfoElemTypeMap.get(elemCode);
151             /*
152             if (type == ELEM_RESULT_CLIST && p.getType().equals(ImpsTags.Login_Response)) {
153                 // Fix up DigestSchema which shares a same code with
154                 // ContactListID. It appears only in Login_Response.
155                 type = ELEM_OTHER_SIMPLE;
156             }
157             */
158         } else {
159             type = ELEM_OTHER_SIMPLE;
160         }
161
162         switch (type) {
163         case ELEM_SESSION_ID:
164             if (elemValue.mStrValue == null) {
165                 throw new ParserException("Element SessionID must have string value!");
166             }
167
168             if (p.getType().equals(ImpsTags.Login_Response)) {
169                 p.addElement(ImpsTags.SessionID, elemValue.mStrValue);
170             } else {
171                 p.setSession(elemValue.mStrValue);
172             }
173             break;
174
175         case ELEM_RESULT:
176             // ST=<StatusCode>
177             // ST=(<StatusCode>,<Description>)
178             PrimitiveElement result = p.addElement(ImpsTags.Result);
179
180             if (elemValue.mStrValue != null) {
181                 result.addChild(ImpsTags.Code, elemValue.mStrValue);
182             } else {
183                 checkGroupValue(elemValue.mValueGroup, 2);
184
185                 result.addChild(ImpsTags.Code, elemValue.mValueGroup.get(0).mStrValue);
186                 result.addChild(ImpsTags.Description, elemValue.mValueGroup.get(1).mStrValue);
187             }
188             break;
189
190         case ELEM_ALL_FUNCTIONS:
191         case ELEM_NOT_AVAIL_FUNCS:
192             p.addElement(translateServiceTree(elemCode, elemValue));
193             break;
194
195         case ELEM_CAPABILITY_LIST:
196             p.addElement(translateCapabilityList(elemValue));
197             break;
198
199         case ELEM_CONTACT_LIST:
200             if (elemValue.mStrValue != null) {
201                 p.addElement(ImpsTags.ContactList, elemValue.mStrValue);
202             } else {
203                 checkGroupValue(elemValue.mValueGroup, UNCERTAIN_GROUP_SIZE);
204                 for (ParamValue value : elemValue.mValueGroup) {
205                     p.addElement(ImpsTags.ContactList, value.mStrValue);
206                 }
207             }
208             break;
209
210         case ELEM_DEFAULT_CONTACT_LIST:
211             if (elemValue.mStrValue == null) {
212                 throw new ParserException("Deafult Contact List must have string value!");
213             }
214
215             p.addElement(ImpsTags.DefaultContactList, elemValue.mStrValue);
216             break;
217
218         case ELEM_USER_NICK_LIST:
219         {
220             checkGroupValue(elemValue.mValueGroup, UNCERTAIN_GROUP_SIZE);
221
222             PrimitiveElement nicklistElem = p.addElement(ImpsTags.NickList);
223
224             int groupSize = elemValue.mValueGroup.size();
225             for (int i = 0; i < groupSize; i++) {
226                 ArrayList<ParamValue> valueGroup = elemValue.mValueGroup.get(i).mValueGroup;
227                 checkGroupValue(valueGroup, 2);
228
229                 String nickname = valueGroup.get(0).mStrValue;
230                 String address  = valueGroup.get(1).mStrValue;
231                 if (nickname == null || address == null) {
232                     throw new ParserException("Null value found for NickName: " + nickname
233                             + "-" + address);
234                 }
235
236                 PrimitiveElement nicknameElem = nicklistElem.addChild(ImpsTags.NickName);
237                 nicknameElem.addChild(ImpsTags.Name, "".equals(nickname) ? null : nickname);
238                 nicknameElem.addChild(ImpsTags.UserID, address);
239             }
240         }
241             break;
242
243         case ELEM_CONTACT_LIST_PROPS:
244         {
245             checkGroupValue(elemValue.mValueGroup, UNCERTAIN_GROUP_SIZE);
246
247             PrimitiveElement propertiesElem = p.addElement(ImpsTags.ContactListProperties);
248
249             int groupSize = elemValue.mValueGroup.size();
250             for (int i = 0; i < groupSize; i++) {
251                 ArrayList<ParamValue> valueGroup = elemValue.mValueGroup.get(i).mValueGroup;
252                 checkGroupValue(valueGroup, 2);
253
254                 String name  = valueGroup.get(0).mStrValue;
255                 String value = valueGroup.get(1).mStrValue;
256                 if (name == null || value == null) {
257                     throw new ParserException("Null value found for property: " + name + "-" + value);
258                 }
259
260                 if (PtsCodes.DisplayName.equals(name)) {
261                     name = ImpsConstants.DisplayName;
262                 } else if (PtsCodes.Default.equals(name)) {
263                     name = ImpsConstants.Default;
264                 } else {
265                     throw new ParserException("Unrecognized property " + name);
266                 }
267
268                 PrimitiveElement propertyElem = propertiesElem.addChild(ImpsTags.Property);
269                 propertyElem.addChild(ImpsTags.Name, name);
270                 propertyElem.addChild(ImpsTags.Value, value);
271             }
272         }
273             break;
274
275         case ELEM_PRESENCE:
276             //PR=(<UserID>[,<PresenceSubList>])
277             //PR=((<UserID>[,<PresenceSubList>]),(<UserID>[,<PresenceSubList>]))
278             checkGroupValue(elemValue.mValueGroup, UNCERTAIN_GROUP_SIZE);
279
280             if (elemValue.mValueGroup.size() == 1) {
281                 // PR=(<UserID>)
282                 ParamValue value = elemValue.mValueGroup.get(0);
283                 if (value.mStrValue != null) {
284                     p.addElement(ImpsTags.Presence).addChild(ImpsTags.UserID, value.mStrValue);
285                 } else {
286                     // workaround for OZ server
287                     p.addElement(translatePresence(value.mValueGroup));
288                 }
289
290             } else {
291                 if (elemValue.mValueGroup.get(0).mStrValue == null) {
292                     // PR=((<UserID>[,<PresenceSubList>]),(<UserID>[,<PresenceSubList>]))
293                     int groupSize = elemValue.mValueGroup.size();
294                     for (int i = 0; i < groupSize; i++) {
295                         ParamValue value = elemValue.mValueGroup.get(i);
296                         if (value.mStrValue != null) {
297                             p.addElement(ImpsTags.Presence).addChild(ImpsTags.UserID, value.mStrValue);
298                         } else {
299                             p.addElement(translatePresence(value.mValueGroup));
300                         }
301                     }
302                 } else {
303                     // PR=(<UserID>,<PresenceSubList>)
304                     p.addElement(translatePresence(elemValue.mValueGroup));
305                 }
306             }
307             break;
308
309         case ELEM_OTHER_SIMPLE:
310             p.addElement(translateSimpleElem(elemCode, elemValue));
311             break;
312
313         default:
314             throw new ParserException("Unsupported element " + elemValue);
315         }
316     }
317
318     private static PrimitiveElement translatePresence(ArrayList<ParamValue> valueGroup)
319             throws ParserException {
320         checkGroupValue(valueGroup, UNCERTAIN_GROUP_SIZE);
321
322         PrimitiveElement presence = new PrimitiveElement(ImpsTags.Presence);
323         if (valueGroup.get(0).mStrValue == null) {
324             throw new ParserException("UserID must have string value!");
325         }
326         presence.addChild(ImpsTags.UserID, valueGroup.get(0).mStrValue);
327
328         if (valueGroup.size() > 1) {
329             // has presence sub list
330             presence.addChild(translatePresenceSubList(valueGroup.get(1)));
331         }
332
333         return presence;
334     }
335
336     private static PrimitiveElement translatePresenceSubList(ParamValue value)
337             throws ParserException {
338         checkGroupValue(value.mValueGroup, UNCERTAIN_GROUP_SIZE);
339
340         PrimitiveElement presenceSubList = new PrimitiveElement(ImpsTags.PresenceSubList);
341
342         int groupSize = value.mValueGroup.size();
343         for (int i = 0; i < groupSize; i++) {
344             ParamValue v = value.mValueGroup.get(i);
345             if (v.mStrValue != null) {
346                 throw new ParserException("Unexpected string value for presence attribute");
347             }
348
349             presenceSubList.addChild(translatePresenceAttribute(v.mValueGroup));
350         }
351
352         return presenceSubList;
353     }
354
355     // <attribute>[,<qualifier>][,<value>]
356     // <attribute>[,<qualifier>,<sub-attribute>]
357     private static PrimitiveElement translatePresenceAttribute(
358             ArrayList<ParamValue> valueGroup) throws ParserException {
359         String type = valueGroup.get(0).mStrValue;
360         if (type == null) {
361             return null;
362         }
363
364         String tag = PtsCodes.getPresenceAttributeElement(type);
365         if (tag == null) {
366             return null;
367         }
368
369         PrimitiveElement paElem = new PrimitiveElement(tag);
370         if (valueGroup.size() == 2) {
371             // no qualifier
372             translateAttributeValue(paElem, valueGroup.get(1), false);
373         }else if (valueGroup.size() == 3) {
374             // has qualifier, and it should has no group value
375             ParamValue qualifierValue = valueGroup.get(1);
376             if (qualifierValue.mStrValue == null) {
377                 throw new ParserException("Qualifier value can't be group value!");
378             }
379
380             if (!"".equals(qualifierValue.mStrValue)) {
381                 paElem.addChild(ImpsTags.Qualifier, qualifierValue.mStrValue);
382             }
383
384             translateAttributeValue(paElem, valueGroup.get(2), true);
385         } else {
386             return null;
387         }
388
389         return paElem;
390     }
391
392     private static void translateAttributeValue(PrimitiveElement paElem,
393             ParamValue v, boolean hasQualifier) throws ParserException {
394         if (v.mStrValue == null) {
395             // sub-attribute as value
396             checkGroupValue(v.mValueGroup, UNCERTAIN_GROUP_SIZE);
397             if (v.mValueGroup.get(0).mStrValue != null) {
398                 paElem.addChild(translatePresenceAttribute(v.mValueGroup));
399             } else {
400                 int groupSize = v.mValueGroup.size();
401                 for (int i = 0; i < groupSize; i++) {
402                     ParamValue value = v.mValueGroup.get(i);
403                     if (value.mStrValue != null) {
404                         throw new ParserException("Presence Attribute value error!");
405                     }
406
407                     checkGroupValue(value.mValueGroup, UNCERTAIN_GROUP_SIZE);
408                     paElem.addChild(translatePresenceAttribute(value.mValueGroup));
409                 }
410             }
411         } else {
412             // single simple value
413             if (hasQualifier) {
414                 paElem.addChild(ImpsTags.PresenceValue, PtsCodes.getPAValue(v.mStrValue));
415             } else {
416                 paElem.setContents(PtsCodes.getPAValue(v.mStrValue));
417             }
418         }
419     }
420
421     private static void checkGroupValue(ArrayList<ParamValue> valueGroup,
422             int expectedGroupSize) throws ParserException {
423         if (valueGroup == null
424                 || (expectedGroupSize != UNCERTAIN_GROUP_SIZE
425                         && valueGroup.size() != expectedGroupSize)) {
426             throw new ParserException("Invalid group value!");
427         }
428
429         int groupSize = valueGroup.size();
430         for (int i = 0; i < groupSize; i++) {
431             if (valueGroup.get(i) == null) {
432                 throw new ParserException("Invalid group value!");
433             }
434         }
435     }
436
437     private static PrimitiveElement translateCapabilityList(ParamValue elemValue)
438             throws ParserException {
439         PrimitiveElement elem = new PrimitiveElement(ImpsTags.AgreedCapabilityList);
440         ArrayList<ParamValue> params = elemValue.mValueGroup;
441         if (params != null) {
442             checkGroupValue(params, UNCERTAIN_GROUP_SIZE);
443             int paramsSize = params.size();
444             for (int i = 0; i < paramsSize; i++) {
445                 ArrayList<ParamValue> capElemGroup = params.get(i).mValueGroup;
446                 checkGroupValue(capElemGroup, 2);
447
448                 String capElemCode = capElemGroup.get(0).mStrValue;
449                 String capElemName;
450                 if (capElemCode == null
451                         || (capElemName = PtsCodes.getCapElement(capElemCode)) == null) {
452                     throw new ParserException("Unknown capability element "
453                             + capElemCode);
454                 }
455                 String capElemValue = capElemGroup.get(1).mStrValue;
456                 if (capElemValue == null) {
457                     throw new ParserException("Illegal capability value for "
458                             + capElemCode);
459                 }
460                 capElemValue = PtsCodes.getCapValue(capElemValue);
461
462                 elem.addChild(capElemName, capElemValue);
463             }
464         }
465         return elem;
466     }
467
468     private static PrimitiveElement translateServiceTree(String elemCode,
469             ParamValue elemValue) throws ParserException {
470         String elemName = PtsCodes.getElement(elemCode);
471         PrimitiveElement elem = new PrimitiveElement(elemName);
472         // TODO: translate the service tree.
473         return elem;
474     }
475
476     private static PrimitiveElement translateSimpleElem(String elemCode, ParamValue value)
477             throws ParserException {
478         String elemName = PtsCodes.getElement(elemCode);
479         if (elemName == null) {
480             throw new ParserException("Unrecognized parameter " + elemCode);
481         }
482
483         PrimitiveElement elem = new PrimitiveElement(elemName);
484         if (value.mStrValue != null) {
485             elem.setContents(value.mStrValue);
486         } else {
487             throw new ParserException("Don't know how to handle parameters for "
488                     + elemName);
489         }
490
491         return elem;
492     }
493
494     private HashMap<String, ParamValue> parseParams() throws ParserException {
495         int pos = mPos;
496         StringBuilder buf = mStringBuf;
497         int len = buf.length();
498         HashMap<String, ParamValue> ret = new HashMap<String, ParamValue>();
499
500         String paramName;
501         ParamValue paramValue;
502
503         while (pos < len) {
504             int nameStart = pos;
505             while (pos < len) {
506                 char ch = buf.charAt(pos);
507                 if (ch == ' ' || ch == '=') {
508                     break;
509                 }
510                 pos++;
511             }
512             if (nameStart == pos) {
513                 throw new ParserException("Missing parameter name near " + pos);
514             }
515             paramName = buf.substring(nameStart, pos);
516             if (pos < len && buf.charAt(pos) == '=') {
517                 pos++;
518                 mPos = pos;
519                 paramValue = parseParamValue();
520                 pos = mPos;
521             } else {
522                 paramValue = null;
523             }
524             ret.put(paramName, paramValue);
525
526             if (pos < len) {
527                 // more parameters ahead
528                 match(' ');
529                 pos = mPos;
530             }
531         }
532
533         return ret;
534     }
535
536     private ParamValue parseParamValue() throws ParserException {
537         int pos = mPos;
538         StringBuilder buf = mStringBuf;
539         int len = buf.length();
540
541         if (pos == len) {
542             throw new ParserException("Missing parameter value near " + pos);
543         }
544         ParamValue value = new ParamValue();
545
546         char ch = buf.charAt(pos);
547         if (ch == '(') {
548             // value list
549             pos++;
550             ArrayList<ParamValue> valueGroup = new ArrayList<ParamValue>();
551             while (pos < len) {
552                 mPos = pos;
553                 valueGroup.add(parseParamValue());
554                 pos = mPos;
555                 if (pos == len) {
556                     throw new ParserException("Unexpected parameter end");
557                 }
558                 if (buf.charAt(pos) != ',') {
559                     break;
560                 }
561                 pos++;
562             }
563             mPos = pos;
564             match(')');
565             if (valueGroup.isEmpty()) {
566                 throw new ParserException("Empty value group near " + mPos);
567             }
568             value.mValueGroup = valueGroup;
569         } else {
570             // single value
571             if (ch == '"') {
572                 // quoted value
573                 pos++;
574                 StringBuilder escapedValue = new StringBuilder();
575                 boolean quotedEnd = false;
576                 while (pos < len) {
577                     ch = buf.charAt(pos);
578                     pos++;
579                     if (ch == '"') {
580                         if (pos < len && buf.charAt(pos) == '"') {
581                             // "doubled" quote
582                             pos++;
583                         } else {
584                             quotedEnd = true;
585                             break;
586                         }
587                     }
588                     escapedValue.append(ch);
589                 }
590                 if (!quotedEnd) {
591                     throw new ParserException("Unexpected quoted parameter end");
592                 }
593                 value.mStrValue = escapedValue.toString();
594             } else {
595                 int valueStart = pos;
596                 while (pos < len) {
597                     ch = buf.charAt(pos);
598                     if (ch == ',' || ch == ')' || ch == ' ') {
599                         break;
600                     }
601                     if ("\"(=&".indexOf(ch) != -1) {
602                         throw new ParserException("Special character " + ch
603                                 + " must be quoted");
604                     }
605                     pos++;
606                 }
607                 value.mStrValue = buf.substring(valueStart, pos);
608             }
609             mPos = pos;
610         }
611
612         return value;
613     }
614
615     private void match(char c) throws ParserException {
616         if (mStringBuf.charAt(mPos) != c) {
617             throw new ParserException("Expected " + c + " at pos " + mPos);
618         }
619         mPos++;
620     }
621
622     /**
623      * Detect if this short message is a PTS encoded WV-primitive.
624      */
625     public static boolean isPtsPrimitive(CharSequence msg)
626     {
627         if (msg == null) {
628             return false;
629         }
630         Matcher m = sPreamplePattern.matcher(msg);
631         return m.matches();
632     }
633
634     static final class ParamValue {
635         public String mStrValue;
636         public ArrayList<ParamValue> mValueGroup;
637     }
638 }