Packages

AMF decode/encode library

Current section

Files

Jump to
amf src amf.erl
Raw

src/amf.erl

%%% @doc AMF encode/decode functions and AMF value constructors
%%% @reference AMF0 specification: [http://download.macromedia.com/pub/labs/amf/amf0_spec_121207.pdf]
%%% @reference AMF3 specification: [http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/amf/pdf/amf-file-format-spec.pdf]
%%% @end
%%%
%%%
%%% Copyright (c) 2013, Takeru Ohta <phjgt308@gmail.com>
%%%
%%% The MIT License
%%%
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
%%% of this software and associated documentation files (the "Software"), to deal
%%% in the Software without restriction, including without limitation the rights
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
%%% copies of the Software, and to permit persons to whom the Software is
%%% furnished to do so, subject to the following conditions:
%%%
%%% The above copyright notice and this permission notice shall be included in
%%% all copies or substantial portions of the Software.
%%%
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
%%% THE SOFTWARE.
%%%
%%%---------------------------------------------------------------------------------------
-module(amf).
-include("../include/amf.hrl").
%% Encode/Decode API
-export([
decode/2,
encode/2
]).
%% AMF Value Construct API
-export([
object/1, object/2, typed_object/2,
array/1, array/2,
date/1, datetime_to_date/1, msec_to_date/1,
xml/1, xml_document/1,
avmplus_object/1,
byte_array/1,
vector/2, vector/3,
dictionary/1, dictionary/2
]).
%% Exported Types
-export_type([
amf_version/0,
amf_exception/0,
amf_exception_type/0,
amf_value/0,
amf_number/0,
amf_boolean/0,
amf_string/0,
amf_null/0,
amf_undefined/0,
amf_object/0,
amf_array/0,
amf_date/0,
amf_xml_document/0,
amf_xml/0,
amf_byte_array/0,
amf_vector/0,
amf_vector_element_type/0,
amf_dictionary/0,
amf_dictionary_entry/0,
amf_ecma_array/0,
amf_strict_array/0,
amf_avmplus_object/0,
amf_kv_pair/0,
amf_class_name/0,
amf_object_option/0,
amf_vector_option/0,
amf_dictionary_option/0
]).
%%================================================================================
%% Types
%%================================================================================
-type amf_version() :: amf0 | amf3.
-type amf_value() :: amf_number() |
amf_boolean() |
amf_string() |
amf_object() |
amf_null() |
amf_undefined() |
amf_ecma_array() |
amf_strict_array() |
amf_avmplus_object() |
amf_array() |
amf_vector() |
amf_xml_document() |
amf_xml() |
amf_byte_array() |
amf_dictionary() |
amf_date().
-type amf_number() :: number().
-type amf_boolean() :: boolean().
-type amf_string() :: binary().
-type amf_null() :: null.
-type amf_undefined() :: undefined.
-type amf_strict_array() :: [amf_value()].
-type amf_date() :: #amf_date{}.
-type amf_xml_document() :: #amf_xml_document{}.
-type amf_xml() :: #amf_xml{}.
-type amf_avmplus_object() :: #amf_avmplus_object{}.
-type amf_object() :: #amf_object{}.
-type amf_array() :: #amf_array{}.
-type amf_ecma_array() :: #amf_array{values :: []}.
-type amf_byte_array() :: #amf_byte_array{}.
-type amf_vector_element_type() :: int | uint | double | (ClassName::amf_string()).
-type amf_vector() :: #amf_vector{}.
-type amf_dictionary_entry() :: {Key::amf_value(), Value::amf_value()}.
-type amf_dictionary() :: #amf_dictionary{}.
-type amf_kv_pair() :: {Key::amf_string(), Value::amf_value()}.
-type amf_class_name() :: undefined | amf_string(). % ```'undefined' indicates an anonymous class'''
-type amf_exception() :: #amf_exception{}.
-type amf_exception_type() :: invalid | partial | unsupported.
%% ```
%% invalid: Input is something wrong.
%% partial: Input binary is too small to complete decoding.
%% unsupported: Some unsupported feature is needed to proceed.
%%'''
-type amf_object_option() :: {class, amf_class_name()} |
{dynamic, boolean()} |
{sealed_fields, [amf_string()]}.
-type amf_vector_option() :: {variable, boolean()}.
-type amf_dictionary_option() :: {weak, boolean()}.
%%================================================================================
%% Encode/Decode API
%%================================================================================
%% @doc Decode AMF0/AMF3 binary data.
-spec decode(AmfVersion, EncodedBytes) -> {ok, DecodedValue, UnconsumedBytes} | {error, Reason} when
AmfVersion :: amf_version(),
EncodedBytes :: binary(),
DecodedValue :: amf_value(),
UnconsumedBytes :: binary(),
Reason :: amf_exception().
decode(amf0, Bin) -> amf0_decode:decode(Bin);
decode(amf3, Bin) -> amf3_decode:decode(Bin).
%% @doc Encode AMF0/AMF3 value.
-spec encode(AmfVersion, AmfValue) -> {ok, EncodedData} | {error, Reason} when
AmfVersion :: amf_version(),
AmfValue :: amf_value(),
EncodedData :: iolist(),
Reason :: amf_exception().
encode(amf0, Value) -> amf0_encode:encode(Value);
encode(amf3, Value) -> amf3_encode:encode(Value).
%%================================================================================
%% AMF value Construct API
%%================================================================================
%% @doc Make anonymous object.
-spec object(Members) -> amf_object() when Members :: [amf_kv_pair()].
object(Members) -> #amf_object{members = Members}.
%% @doc Make typed-object.
-spec typed_object(TypeName, Members) -> amf_object() when
TypeName :: amf_class_name(),
Members :: [amf_kv_pair()].
typed_object(TypeName, Members) ->
object(Members, [{class, TypeName},
{dynamic, false},
{sealed_fields, [Key || {Key,_} <- Members]}]).
%% @doc Make object.
-spec object(Members, Options) -> amf_object() when
Members :: [amf_kv_pair()],
Options :: [amf_object_option()].
object(Members, Options) ->
#amf_object{members = Members,
class = proplists:get_value(class, Options, undefined),
dynamic = proplists:get_value(dynamic, Options, true),
sealed_fields = proplists:get_value(sealed_fields, Options, [])}.
%% @doc Make associative array.
-spec array(Members) -> amf_array() when Members :: [amf_kv_pair()].
array(Members) -> #amf_array{members = Members}.
%% @doc Make generic array.
-spec array(Values, Members) -> amf_array() when
Values :: [amf_value()],
Members :: [amf_kv_pair()].
array(Values, Members) ->
#amf_array{values = Values, members = Members}.
%% @doc Make date.
-spec date(Timestamp) -> amf_date() when Timestamp :: erlang:timestamp().
date({_,_,_}=Timestamp) -> #amf_date{timestamp=Timestamp}.
%% @doc Make date from timestamp in milliseconds
-spec msec_to_date(MilliSeconds) -> amf_date() when MilliSeconds :: non_neg_integer().
msec_to_date(MilliSecs) ->
MegaSecs = MilliSecs div (1000*1000*1000),
Secs = (MilliSecs rem (1000*1000*1000)) div 1000,
MicroSecs = (MilliSecs rem 1000) * 1000,
date({MegaSecs,Secs,MicroSecs}).
%% @doc Make date from datetime.
-spec datetime_to_date(DateTime) -> amf_date() when DateTime :: calendar:datetime().
datetime_to_date(DateTime) ->
Secs =
calendar:datetime_to_gregorian_seconds(DateTime) -
calendar:datetime_to_gregorian_seconds({{1970,1,1}, {0,0,0}}),
case Secs < 0 of
true -> error({too_old_datetime, DateTime});
false -> msec_to_date(Secs*1000)
end.
%% @doc Make XML Document.
-spec xml_document(Xml) -> amf_xml_document() when Xml :: amf_string().
xml_document(Xml) -> #amf_xml_document{data = Xml}.
%% @doc Make XML.
-spec xml(Xml) -> amf_xml() when Xml :: amf_string().
xml(Xml) -> #amf_xml{data = Xml}.
%% @doc Make AvmPlus Object.
-spec avmplus_object(AmfValue) -> amf_avmplus_object() when AmfValue :: amf_value().
avmplus_object(AmfValue) -> #amf_avmplus_object{value = AmfValue}.
%% @doc Make ByteArray.
-spec byte_array(Bytes) -> amf_byte_array() when Bytes :: binary().
byte_array(Bytes) -> #amf_byte_array{data = Bytes}.
%% @doc Make variable vector.
-spec vector(ElementType, Elements) -> amf_vector() when
ElementType :: amf_vector_element_type(),
Elements :: [amf_value()].
vector(ElementType, Elements) -> vector(ElementType, Elements, []).
%% @doc Make vector.
-spec vector(ElementType, Elements, Options) -> amf_vector() when
ElementType :: amf_vector_element_type(),
Elements :: [amf_value()],
Options :: [amf_vector_option()].
vector(ElementType, Elements, Options) ->
#amf_vector{type = ElementType,
variable = proplists:get_value(variable, Options, true),
elements = Elements}.
%% @doc Make dictionary.
-spec dictionary(Entries) -> amf_dictionary() when Entries :: [amf_dictionary_entry()].
dictionary(Members) -> dictionary(Members, []).
%% @doc Make dictionary.
-spec dictionary(Entries, Options) -> amf_dictionary() when
Entries :: [amf_dictionary_entry()],
Options :: [amf_dictionary_option()].
dictionary(Members, Options) ->
#amf_dictionary{members = Members,
weak = proplists:get_value(weak, Options, false)}.