/* * Flazr <http://flazr.com> Copyright (C) 2009 Peter Thomas. * * This file is part of Flazr. * * Flazr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Flazr is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Flazr. If not, see <http://www.gnu.org/licenses/>. */ package com.flazr.rtmp.client; import static com.flazr.rtmp.client.Amf0Value.MetaDataValuesType.MAP; import java.util.Arrays; import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; import org.jboss.netty.buffer.ChannelBuffer; import android.util.Log; import com.flazr.rtmp.message.TypeEnum; import com.flazr.util.Utils; import com.flazr.util.ValueToEnum; public class Amf0Value { private Amf0Value() {} public static enum MetaDataValuesType implements TypeEnum { NUMBER(0x00), BOOLEAN(0x01), STRING(0x02), OBJECT(0x03), NULL(0x05), UNDEFINED(0x06), MAP(0x08), ARRAY(0x0A), DATE(0x0B), LONG_STRING(0x0C), UNSUPPORTED(0x0D); private final int value; private MetaDataValuesType(int value) { this.value = value; } @Override public int intValue() { return value; } private static final ValueToEnum<MetaDataValuesType> converter = new ValueToEnum<MetaDataValuesType>(MetaDataValuesType.values()); public static MetaDataValuesType valueToEnum(final int value) {//TypeTypeの列挙がconverterに入る return converter.valueToEnum(value); } private static MetaDataValuesType getType(final Object value) { if (value == null) { return NULL; } else if (value instanceof String) { return STRING; } else if (value instanceof Number) { return NUMBER; } else if (value instanceof Boolean) { return BOOLEAN; } else if (value instanceof Amf0Object) { return OBJECT; } else if (value instanceof Map) { return MAP; } else if (value instanceof Object[]) { return ARRAY; } else if(value instanceof Date) { return DATE; } else { throw new RuntimeException("unexpected type: " + value.getClass()); } } } private static final byte BOOLEAN_TRUE = 0x01; private static final byte BOOLEAN_FALSE = 0x00; private static final byte[] OBJECT_END_MARKER = new byte[]{0x00, 0x00, 0x09}; public static void encode(final ChannelBuffer out, final Object value) { final MetaDataValuesType type = MetaDataValuesType.getType(value); out.writeByte((byte) type.value); switch (type) { case NUMBER: if(value instanceof Double) { out.writeLong(Double.doubleToLongBits((Double) value)); } else { // this coverts int also out.writeLong(Double.doubleToLongBits(Double.valueOf(value.toString()))); } return; case BOOLEAN: out.writeByte((Boolean) value ? BOOLEAN_TRUE : BOOLEAN_FALSE); return; case STRING: encodeString(out, (String) value); return; case NULL: return; case MAP: out.writeInt(0); // no break; remaining processing same as OBJECT case OBJECT: final Map<String, Object> map = (Map) value; for(final Map.Entry<String, Object> entry : map.entrySet()) { encodeString(out, entry.getKey()); encode(out, entry.getValue()); } out.writeBytes(OBJECT_END_MARKER,0,OBJECT_END_MARKER.length); return; case ARRAY: final Object[] array = (Object[]) value; out.writeInt(array.length); for(Object o : array) { encode(out, o); } return; case DATE: final long time = ((Date) value).getTime(); out.writeLong(Double.doubleToLongBits(time)); out.writeShort((short) 0); return; default: // ignoring other types client doesn't require for now throw new RuntimeException("unexpected type: " + type); } } private static String decodeString(final ChannelBuffer in) { final short size = in.readShort();//次の2バイトにサイズが入っている final byte[] bytes = new byte[size]; in.readBytes(bytes,0,size); return new String(bytes); // TODO UTF-8 ? } private static void encodeString(final ChannelBuffer out, final String value) { final byte[] bytes = value.getBytes(); // TODO UTF-8 ? out.writeShort((short) bytes.length); out.writeBytes(bytes,0,bytes.length); } public static void encode(final ChannelBuffer out, final Object... values) { for (final Object value : values) { encode(out, value); } } /** * ここでKEY=値が入る * BigEndianHeapChannelBufferのsuper * →HeapChannelBufferのsuper * →AbstractChannelBuffer#readByte()return getByte(readerIndex ++) * →HeapChannelBuffer#getByte(int index)return array[index] * が呼ばれている * @param in * @return */ public static Object decode(final ChannelBuffer in) { byte ba = in.readByte();//1バイト読み込む final MetaDataValuesType type = MetaDataValuesType.valueToEnum(ba);//TypeTypeの列挙の中の引数のインデックスの要素が返ってくる final Object value = decode(in, type); return value; } private static Object decode(final ChannelBuffer in, final MetaDataValuesType type) { switch (type) { case NUMBER: return Double.longBitsToDouble(in.readLong());//数値(0x00)ならLong(8バイト)Abstract#readLong→Heap#getLong読み case BOOLEAN: return in.readByte() == BOOLEAN_TRUE;//BOOLEAN(0x01)ならBOOLEAN_TRUE(0x01)かを返す違う場合(0x00)という事 case STRING: return decodeString(in);//文字列(0x02)なら次の2バイトに文字列長が入っているのでその部分を文字列で返す case ARRAY://配列(0x0A)なら次の4バイトにサイズが入っているのでそれぞれの要素を再帰呼びする final int arraySize = in.readInt(); final Object[] array = new Object[arraySize]; for (int i = 0; i < arraySize; i++) { array[i] = decode(in); } return array; case MAP://MAP(0x08)とOBJECT(0x03)は同じ処置 case OBJECT: final int count; final Map<String, Object> map; if(type == MAP) { count = in.readInt(); //要素数が帰ってくる(メタデータは大概これに入った形になっている) map = new LinkedHashMap<String, Object>(); Log.d("amf0Value","TYPE MAP create MAP length: "+count); } else { Log.d("amf0Value","TYPE OBJECT create Amf0Object "); count = 0; map = new Amf0Object(); } int i = 0; final byte[] endMarker = new byte[3]; while (in.readableBytes() > 0) {//マップのKEY,VALUEのデコード in.getBytes(in.readerIndex(), endMarker,0,3); if (Arrays.equals(endMarker, OBJECT_END_MARKER)) { //今の位置から3バイトがエンドマーカー(0x00, 0x00, 0x09)と同一だったらブレイクする in.skipBytes(3); Log.d("amf0Value","Object end"); break; } if(count > 0 && i++ == count) {//要素数までカウントしてブレイクする→要素数が合っていない場合があるのでOBJECT_END_MARKERじゃないと駄目 Log.d("Amf0Value","TYPE MAP Count END ------------"); // break; } map.put(decodeString(in), decode(in));//文字列と各要素(何重でも入れ子があり得る)を格納する } return map; case DATE://DATE(0x0B)だったら最初の8バイトが実際の日付、次の2バイトがタイムゾーン final long dateValue = in.readLong(); Log.d("Amf0Value","TYPE IS DATE"+dateValue); in.readShort(); // consume the timezone return new Date((long) Double.longBitsToDouble(dateValue)); case LONG_STRING://LONG_STRING(0x0C)だったら最初の4バイト(普通のSTRING(0x02)とはサイズ長の型が違う)に文字列長があるのでその分を取得 Log.d("Amf0Value","LONG_STRING"); final int stringSize = in.readInt(); final byte[] bytes = new byte[stringSize]; in.readBytes(bytes,0,stringSize); return new String(bytes); // TODO UTF-8 ? case NULL: Log.d("Amf0Value","TYPE IS NULL"); case UNDEFINED: Log.d("Amf0Value","TYPE IS UNDEFINED"); case UNSUPPORTED: Log.d("Amf0Value","TYPE IS UNSUPPORTED"); return null; default: throw new RuntimeException("unexpected type: " + type); } } //Logでのみ利用 private static String toString(final MetaDataValuesType type, final Object value) { return type+" "+value; } }