=== added directory '.bzr-builddeb'
=== renamed directory '.bzr-builddeb' => '.bzr-builddeb.moved'
=== added file '.bzr-builddeb/default.conf'
--- .bzr-builddeb/default.conf	1970-01-01 00:00:00 +0000
+++ .bzr-builddeb/default.conf	2015-09-02 10:42:28 +0000
@@ -0,0 +1,2 @@
+[BUILDDEB]
+split = True

=== added directory 'debian'
=== renamed directory 'debian' => 'debian.moved'
=== added file 'debian/changelog'
--- debian/changelog	1970-01-01 00:00:00 +0000
+++ debian/changelog	2015-09-02 10:42:28 +0000
@@ -0,0 +1,150 @@
+nuntium (0.1+14.10.20141013-0ubuntu1) 14.09; urgency=low
+
+  [ Sergio Schvezov ]
+  * Hold off on retrying when ofono fails to activate a context with a
+    generic failure (LP: #1380699)
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Mon, 13 Oct 2014 16:59:11 +0000
+
+nuntium (0.1+14.10.20141002-0ubuntu1) 14.09; urgency=low
+
+  [ Sergio Schvezov ]
+  * Allow context selection over the org.ofono.mms.Service interface
+    (LP: #1370660)
+  * Syncing upload/download operations with activation/deactivation per
+    request. Moving all the ofono context property checks and reflection
+    logic to proper functions for easier reuse and readability. (LP:
+    #1376224)
+  * Retry on NotAttached ofono errors (LP: #1371032)
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Thu, 02 Oct 2014 15:06:31 +0000
+
+nuntium (0.1+14.10.20140924-0ubuntu1) 14.09; urgency=low
+
+  [ Sergio Schvezov ]
+  * Using the same optional parameters android uses for sending, fixing
+    length encoding for lengths < 30, adding extra params to the content
+    types. (LP: #1349299)
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Wed, 24 Sep 2014 12:39:58 +0000
+
+nuntium (0.1+14.10.20140918-0ubuntu1) 14.09; urgency=low
+
+  [ Sergio Schvezov ]
+  * Iterate over a list of possible valid MMS contexts and store the
+    preferred one (LP: #1370659)
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Thu, 18 Sep 2014 04:37:21 +0000
+
+nuntium (0.1+14.10.20140915.1-0ubuntu1) 14.09; urgency=low
+
+  [ Ubuntu daily release ]
+  * New rebuild forced
+
+  [ Sergio Schvezov ]
+  * Add a missing return statement when decoding fails with an
+    additional decoding test. (LP: #1369143)
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Mon, 15 Sep 2014 14:59:46 +0000
+
+nuntium (0.1+14.10.20140912-0ubuntu1) utopic; urgency=low
+
+  [ Sergio Schvezov ]
+  * Allow proxyless contexts to be selected for MMS (LP: #1362008)
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Fri, 12 Sep 2014 00:38:34 +0000
+
+nuntium (0.1+14.10.20140904-0ubuntu1) utopic; urgency=low
+
+  [ Sergio Schvezov ]
+  * Better error handling on sending MMS and lowering the supported
+    version number for broader carrier support (LP: #1349299)
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Thu, 04 Sep 2014 19:21:58 +0000
+
+nuntium (0.1+14.10.20140902-0ubuntu1) utopic; urgency=low
+
+  [ Sergio Schvezov ]
+  * Read reflected content length for push data into the correct type.
+    (LP: #1342441)
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Tue, 02 Sep 2014 08:39:25 +0000
+
+nuntium (0.1+14.10.20140814-0ubuntu1) utopic; urgency=low
+
+  [ Tiago Salem Herrmann ]
+  * Add ModemObjectPath property to Service.
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Thu, 14 Aug 2014 20:25:23 +0000
+
+nuntium (0.1+14.10.20140721-0ubuntu1) utopic; urgency=low
+
+  [ Sergio Schvezov ]
+  * Calling sync and close after encoding and before uploading.
+  * Header parameter encoding corrections for content type and from
+    token insert address length.
+  * Attachment encoding fixes. (LP: #1342270)
+  * Tracking response file for uploads (remaining parts to be done when
+    telepathy-ofono integration work starts)
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Mon, 21 Jul 2014 09:44:10 +0000
+
+nuntium (0.1+14.10.20140702.2-0ubuntu1) utopic; urgency=low
+
+  [ CI bot ]
+  * Upload support while moving udm to it's new package namespace
+  * SendMessage telepathy service support with necessary encoder fixes.
+
+  [ Sergio Schvezov ]
+  * Improving incoming dbus method call handling for the mms service
+    interface
+  * Waiting for calls to GetServices on the proper interface
+  * Adding a minimal telepathy message interface
+  * Fixing recv for multiple recipients.
+  * Making decoding less verbose and logging information on errors only
+  * Adding encode -> decode tests for the cases where decoding is the
+    inverse function of encoding.
+  * Relaxing matching for smil media type
+  * Length bound checks fix (LP: #1336146)
+  * Sending MessageAdded with draft Status for new outgoing messages
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Wed, 02 Jul 2014 21:15:22 +0000
+
+nuntium (0.1+14.10.20140621-0ubuntu1) utopic; urgency=low
+
+  [ Sergio Schvezov ]
+  * Decoding well known media types in the push notification. (LP:
+    #1330917)
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Sat, 21 Jun 2014 19:53:48 +0000
+
+nuntium (0.1+14.10.20140529-0ubuntu1) utopic; urgency=low
+
+  [ Sergio Schvezov ]
+  * Fixing constrained content type decoding (LP: #1324182)
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Thu, 29 May 2014 13:55:29 +0000
+
+nuntium (0.1+14.10.20140514.1-0ubuntu1) utopic; urgency=low
+
+  [ Sergio Schvezov ]
+  * Fixing a string decode bounds issue
+  * Removing redundant log line
+  * Adding one more condition to the upstart start stanza since the
+    first event can be missed
+  * Splitting up ofono package to smaller logical bits
+  * Encoder improvements
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com>  Wed, 14 May 2014 14:21:59 +0000
+
+nuntium (0.1-0ubuntu2) utopic; urgency=medium
+
+  * Moving stray Built-Using from Depends into Built-Using.
+
+ -- Sergio Schvezov <sergio.schvezov@canonical.com>  Mon, 12 May 2014 10:04:25 -0300
+
+nuntium (0.1-0ubuntu1) utopic; urgency=low
+
+  * Initial packaging. 
+
+ -- Sergio Schvezov <sergio.schvezov@canonical.com>  Thu, 03 Apr 2014 15:01:24 -0300

=== added file 'debian/compat'
--- debian/compat	1970-01-01 00:00:00 +0000
+++ debian/compat	2015-09-02 10:42:28 +0000
@@ -0,0 +1,1 @@
+9

=== added file 'debian/control'
--- debian/control	1970-01-01 00:00:00 +0000
+++ debian/control	2015-09-02 10:42:28 +0000
@@ -0,0 +1,57 @@
+Source: nuntium
+Section: devel
+Priority: optional
+Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
+Build-Depends:
+ debhelper (>= 9),
+ dh-golang,
+ gccgo,
+ gccgo-go,
+ golang-go-dbus-dev,
+ golang-go-xdg-dev,
+ golang-gocheck-dev,
+ golang-udm-dev,
+Standards-Version: 3.9.5
+Homepage: https://launchpad.net/nuntium
+Vcs-Browser: http://bazaar.launchpad.net/~phablet-team/nuntium/trunk/files
+Vcs-Bzr: lp:nuntium
+
+Package: nuntium
+Architecture: any
+Depends: ofono, ubuntu-download-manager, ubuntu-upload-manager, ${misc:Depends}, ${shlibs:Depends}
+Built-Using: ${misc:Built-Using}
+Recommends: telepathy-ofono
+Conflicts: mmsd
+Description: Bridges push notifications from ofono to telepathy-ofono
+ This component registers a push agent with ofono and handles the MMS workflow
+ by bridging with telepathy-ofono
+
+Package: nuntium-decode-cli
+Architecture: any
+Depends: ${misc:Depends}, ${shlibs:Depends}
+Built-Using: ${misc:Built-Using}
+Description: Decode m-retrieve.conf messages
+ Decodes and prints out decoding results to the
+
+Package: golang-nuntium-mms-dev
+Architecture: all
+Depends: ${misc:Depends}
+Built-Using: ${misc:Built-Using}
+Description: Go library for manipulating MMS
+ This package handles MMS PDUs and has hooks for related actions in the MMS
+ workflow
+
+Package: golang-nuntium-ofono-dev
+Architecture: all
+Depends: ${misc:Depends}
+Built-Using: ${misc:Built-Using}
+Description: Go library for interfacing with ofono
+ Provides facilities to interface with ofono with regards to MMS through dbus
+
+Package: golang-nuntium-telepathy-dev
+Architecture: all
+Depends: ${misc:Depends}
+Built-Using: ${misc:Built-Using}
+Description: Go library for interfacing with telepathy-ofono
+ Provides facilities to interface with telepathy ofono with regards to MMS
+ through dbus

=== added file 'debian/copyright'
--- debian/copyright	1970-01-01 00:00:00 +0000
+++ debian/copyright	2015-09-02 10:42:28 +0000
@@ -0,0 +1,22 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: nuntium
+Source: https://launchpad.net/nuntium
+
+Files: *
+Copyright: Copyright (C) 2013 Canonical, Ltd.
+License: GPL-3
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the the GNU General Public License version 3, as
+ published by the Free Software Foundation.
+ .
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranties of
+ MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
+ PURPOSE.  See the applicable version of the GNU Lesser General Public
+ License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ .
+ On Debian systems, the complete text of the GNU General Public License
+ can be found in `/usr/share/common-licenses/GPL-3'

=== added file 'debian/golang-nuntium-mms-dev.install'
--- debian/golang-nuntium-mms-dev.install	1970-01-01 00:00:00 +0000
+++ debian/golang-nuntium-mms-dev.install	2015-09-02 10:42:28 +0000
@@ -0,0 +1,1 @@
+usr/share/gocode/src/launchpad.net/nuntium/mms

=== added file 'debian/golang-nuntium-ofono-dev.install'
--- debian/golang-nuntium-ofono-dev.install	1970-01-01 00:00:00 +0000
+++ debian/golang-nuntium-ofono-dev.install	2015-09-02 10:42:28 +0000
@@ -0,0 +1,1 @@
+usr/share/gocode/src/launchpad.net/nuntium/ofono

=== added file 'debian/golang-nuntium-telepathy-dev.install'
--- debian/golang-nuntium-telepathy-dev.install	1970-01-01 00:00:00 +0000
+++ debian/golang-nuntium-telepathy-dev.install	2015-09-02 10:42:28 +0000
@@ -0,0 +1,1 @@
+usr/share/gocode/src/launchpad.net/nuntium/telepathy

=== added file 'debian/nuntium-decode-cli.install'
--- debian/nuntium-decode-cli.install	1970-01-01 00:00:00 +0000
+++ debian/nuntium-decode-cli.install	2015-09-02 10:42:28 +0000
@@ -0,0 +1,1 @@
+usr/bin/nuntium-decode-cli

=== added file 'debian/nuntium.conf'
--- debian/nuntium.conf	1970-01-01 00:00:00 +0000
+++ debian/nuntium.conf	2015-09-02 10:42:28 +0000
@@ -0,0 +1,10 @@
+description "nuntium service binds ofono and telepathy-ofono for MMS"
+
+# Start on session dbus is spawned or
+#     org.ofono bus name appears on system dbus
+# Stop on org.ofono bus name disappearing on system dbus
+
+start on (started dbus or dbus SIGNAL='NameOwnerChanged' BUS='system' INTERFACE='org.freedesktop.DBus' OBJPATH='/org/freedesktop/DBus' SENDER='org.freedesktop.DBus' ARG0='org.ofono' ARG1='' ARG2='*')
+stop on dbus SIGNAL='NameOwnerChanged' BUS='system' INTERFACE='org.freedesktop.DBus' OBJPATH='/org/freedesktop/DBus' SENDER='org.freedesktop.DBus' ARG0='org.ofono' ARG1='*' ARG2=''
+
+exec nuntium

=== added file 'debian/nuntium.install'
--- debian/nuntium.install	1970-01-01 00:00:00 +0000
+++ debian/nuntium.install	2015-09-02 10:42:28 +0000
@@ -0,0 +1,2 @@
+debian/nuntium.conf /usr/share/upstart/sessions/
+usr/bin/nuntium

=== added file 'debian/rules'
--- debian/rules	1970-01-01 00:00:00 +0000
+++ debian/rules	2015-09-02 10:42:28 +0000
@@ -0,0 +1,33 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+
+export DH_OPTIONS
+export DH_GOPKG := launchpad.net/nuntium
+export DH_GOLANG_INSTALL_ALL := 1
+
+DEB_HOST_ARCH := $(shell dpkg-architecture -qDEB_HOST_ARCH)
+
+%:
+	dh $@ \
+		--buildsystem=golang \
+		--with=golang \
+		--fail-missing
+
+override_dh_auto_test:
+# The test runners panic when running on powerpc64.
+ifneq ($(DEB_HOST_ARCH),powerpc)
+	dh_auto_test
+endif
+	
+override_dh_auto_install:
+	dh_auto_install -O--buildsystem=golang
+	mv ${CURDIR}/debian/tmp/usr/bin/decode-cli \
+		${CURDIR}/debian/tmp/usr/bin/nuntium-decode-cli 
+	rm -r \
+		${CURDIR}/debian/tmp/usr/bin/test \
+		${CURDIR}/debian/tmp/usr/share/gocode/src/$(DH_GOPKG)/test \
+		${CURDIR}/debian/tmp/usr/share/gocode/src/$(DH_GOPKG)/storage \
+		${CURDIR}/debian/tmp/usr/share/gocode/src/$(DH_GOPKG)/*.go
+
+override_dh_strip:
+	echo "Skipping strip"

=== added directory 'doc'
=== added file 'loop.go'
--- loop.go	1970-01-01 00:00:00 +0000
+++ loop.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of nuntium.
+ *
+ * nuntium is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * nuntium 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package main
+
+import (
+	"log"
+	"os"
+	"os/signal"
+	"syscall"
+)
+
+type Mainloop struct {
+	sigchan  chan os.Signal
+	termchan chan int
+	Bindings map[os.Signal]func()
+}
+
+/*
+Start the mainloop.
+
+This method will block its current thread. The best spot for calling this
+method is right near the bottom of your application's main() function.
+*/
+func (m *Mainloop) Start() {
+	sigs := make([]os.Signal, len(m.Bindings))
+	for s, _ := range m.Bindings {
+		sigs = append(sigs, s)
+	}
+	signal.Notify(m.sigchan, sigs...)
+L:
+	for {
+		select {
+		case sig := <-m.sigchan:
+			log.Print("Received ", sig)
+			m.Bindings[sig]()
+		case _ = <-m.termchan:
+			break L
+		}
+	}
+	return
+}
+
+/*
+Stops the mainloop.
+*/
+func (m *Mainloop) Stop() {
+	go func() { m.termchan <- 1 }()
+	return
+}
+
+func HupHandler() {
+	syscall.Exit(1)
+}
+
+func IntHandler() {
+	syscall.Exit(1)
+}

=== added file 'main.go'
--- main.go	1970-01-01 00:00:00 +0000
+++ main.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of nuntium.
+ *
+ * nuntium is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * nuntium 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package main
+
+import (
+	"log"
+	"os"
+	"syscall"
+
+	"launchpad.net/go-dbus/v1"
+	"launchpad.net/nuntium/ofono"
+	"launchpad.net/nuntium/telepathy"
+)
+
+func main() {
+	var (
+		conn        *dbus.Connection
+		connSession *dbus.Connection
+		err         error
+	)
+	if connSession, err = dbus.Connect(dbus.SessionBus); err != nil {
+		log.Fatal("Connection error: ", err)
+	}
+	log.Print("Using session bus on ", connSession.UniqueName)
+
+	mmsManager, err := telepathy.NewMMSManager(connSession)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	if conn, err = dbus.Connect(dbus.SystemBus); err != nil {
+		log.Fatal("Connection error: ", err)
+	}
+	log.Print("Using system bus on ", conn.UniqueName)
+
+	modemManager := ofono.NewModemManager(conn)
+	mediators := make(map[dbus.ObjectPath]*Mediator)
+	go func() {
+		for {
+			select {
+			case modem := <-modemManager.ModemAdded:
+				mediators[modem.Modem] = NewMediator(modem)
+				go mediators[modem.Modem].init(mmsManager)
+				if err := modem.Init(); err != nil {
+					log.Printf("Cannot initialize modem %s", modem.Modem)
+				}
+			case modem := <-modemManager.ModemRemoved:
+				mediators[modem.Modem].Delete()
+			}
+		}
+	}()
+
+	if err := modemManager.Init(); err != nil {
+		log.Fatal(err)
+	}
+
+	m := Mainloop{
+		sigchan:  make(chan os.Signal, 1),
+		termchan: make(chan int),
+		Bindings: make(map[os.Signal]func())}
+
+	m.Bindings[syscall.SIGHUP] = func() { m.Stop(); HupHandler() }
+	m.Bindings[syscall.SIGINT] = func() { m.Stop(); IntHandler() }
+	m.Start()
+}

=== added file 'mediator.go'
--- mediator.go	1970-01-01 00:00:00 +0000
+++ mediator.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,403 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of nuntium.
+ *
+ * nuntium is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * nuntium 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package main
+
+import (
+	"io/ioutil"
+	"log"
+	"os"
+	"sync"
+
+	"launchpad.net/nuntium/mms"
+	"launchpad.net/nuntium/ofono"
+	"launchpad.net/nuntium/storage"
+	"launchpad.net/nuntium/telepathy"
+)
+
+type Mediator struct {
+	modem                 *ofono.Modem
+	telepathyService      *telepathy.MMSService
+	NewMNotificationInd   chan *mms.MNotificationInd
+	NewMNotifyRespInd     chan *mms.MNotifyRespInd
+	NewMRetrieveConf      chan *mms.MRetrieveConf
+	NewMSendReq           chan *mms.MSendReq
+	NewMRetrieveConfFile  chan string
+	NewMNotifyRespIndFile chan string
+	NewMSendReqFile       chan struct{ filePath, uuid string }
+	outMessage            chan *telepathy.OutgoingMessage
+	terminate             chan bool
+	contextLock           sync.Mutex
+}
+
+//TODO these vars need a configuration location managed by system settings or
+//some UI accessible location.
+//useDeliveryReports is set in ofono
+var (
+	deferredDownload   bool
+	useDeliveryReports bool
+)
+
+func NewMediator(modem *ofono.Modem) *Mediator {
+	mediator := &Mediator{modem: modem}
+	mediator.NewMNotificationInd = make(chan *mms.MNotificationInd)
+	mediator.NewMRetrieveConf = make(chan *mms.MRetrieveConf)
+	mediator.NewMRetrieveConfFile = make(chan string)
+	mediator.NewMNotifyRespInd = make(chan *mms.MNotifyRespInd)
+	mediator.NewMNotifyRespIndFile = make(chan string)
+	mediator.NewMSendReq = make(chan *mms.MSendReq)
+	mediator.NewMSendReqFile = make(chan struct{ filePath, uuid string })
+	mediator.outMessage = make(chan *telepathy.OutgoingMessage)
+	mediator.terminate = make(chan bool)
+	return mediator
+}
+
+func (mediator *Mediator) Delete() {
+	mediator.terminate <- mediator.telepathyService == nil
+}
+
+func (mediator *Mediator) init(mmsManager *telepathy.MMSManager) {
+mediatorLoop:
+	for {
+		select {
+		case push, ok := <-mediator.modem.PushAgent.Push:
+			if !ok {
+				log.Print("PushChannel is closed")
+				continue
+			}
+			go mediator.handleMNotificationInd(push)
+		case mNotificationInd := <-mediator.NewMNotificationInd:
+			if deferredDownload {
+				go mediator.handleDeferredDownload(mNotificationInd)
+			} else {
+				go mediator.getMRetrieveConf(mNotificationInd)
+			}
+		case mRetrieveConfFilePath := <-mediator.NewMRetrieveConfFile:
+			go mediator.handleMRetrieveConf(mRetrieveConfFilePath)
+		case mRetrieveConf := <-mediator.NewMRetrieveConf:
+			go mediator.handleRetrieved(mRetrieveConf)
+		case mNotifyRespInd := <-mediator.NewMNotifyRespInd:
+			go mediator.handleMNotifyRespInd(mNotifyRespInd)
+		case mNotifyRespIndFilePath := <-mediator.NewMNotifyRespIndFile:
+			go mediator.sendMNotifyRespInd(mNotifyRespIndFilePath)
+		case msg := <-mediator.outMessage:
+			go mediator.handleOutgoingMessage(msg)
+		case mSendReq := <-mediator.NewMSendReq:
+			go mediator.handleMSendReq(mSendReq)
+		case mSendReqFile := <-mediator.NewMSendReqFile:
+			go mediator.sendMSendReq(mSendReqFile.filePath, mSendReqFile.uuid)
+		case id := <-mediator.modem.IdentityAdded:
+			var err error
+			mediator.telepathyService, err = mmsManager.AddService(id, mediator.modem.Modem, mediator.outMessage, useDeliveryReports)
+			if err != nil {
+				log.Fatal(err)
+			}
+		case id := <-mediator.modem.IdentityRemoved:
+			err := mmsManager.RemoveService(id)
+			if err != nil {
+				log.Fatal(err)
+			}
+			mediator.telepathyService = nil
+		case ok := <-mediator.modem.PushInterfaceAvailable:
+			if ok {
+				if err := mediator.modem.PushAgent.Register(); err != nil {
+					log.Fatal(err)
+				}
+			} else {
+				if err := mediator.modem.PushAgent.Unregister(); err != nil {
+					log.Fatal(err)
+				}
+			}
+		case terminate := <-mediator.terminate:
+			/*
+				close(mediator.terminate)
+				close(mediator.outMessage)
+				close(mediator.NewMNotificationInd)
+				close(mediator.NewMRetrieveConf)
+				close(mediator.NewMRetrieveConfFile)
+				close(mediator.NewMNotifyRespInd)
+				close(mediator.NewMNotifyRespIndFile)
+				close(mediator.NewMSendReq)
+				close(mediator.NewMSendReqFile)
+			*/
+			if terminate {
+				break mediatorLoop
+			}
+		}
+	}
+	log.Print("Ending mediator instance loop for modem")
+}
+
+func (mediator *Mediator) handleMNotificationInd(pushMsg *ofono.PushPDU) {
+	if pushMsg == nil {
+		log.Print("Received nil push")
+		return
+	}
+	dec := mms.NewDecoder(pushMsg.Data)
+	mNotificationInd := mms.NewMNotificationInd()
+	if err := dec.Decode(mNotificationInd); err != nil {
+		log.Println("Unable to decode m-notification.ind: ", err, "with log", dec.GetLog())
+		return
+	}
+	storage.Create(mNotificationInd.UUID, mNotificationInd.ContentLocation)
+	mediator.NewMNotificationInd <- mNotificationInd
+}
+
+func (mediator *Mediator) handleDeferredDownload(mNotificationInd *mms.MNotificationInd) {
+	//TODO send MessageAdded with status="deferred" and mNotificationInd relevant headers
+}
+
+func (mediator *Mediator) getMRetrieveConf(mNotificationInd *mms.MNotificationInd) {
+	mediator.contextLock.Lock()
+	defer mediator.contextLock.Unlock()
+
+	preferredContext, _ := mediator.telepathyService.GetPreferredContext()
+	mmsContext, err := mediator.modem.ActivateMMSContext(preferredContext)
+	if err != nil {
+		log.Print("Cannot activate ofono context: ", err)
+		return
+	}
+	defer func() {
+		if err := mediator.modem.DeactivateMMSContext(mmsContext); err != nil {
+			log.Println("Issues while deactivating context:", err)
+		}
+	}()
+
+	if err := mediator.telepathyService.SetPreferredContext(mmsContext.ObjectPath); err != nil {
+		log.Println("Unable to store the preferred context for MMS:", err)
+	}
+	proxy, err := mmsContext.GetProxy()
+	if err != nil {
+		log.Print("Error retrieving proxy: ", err)
+		return
+	}
+	if filePath, err := mNotificationInd.DownloadContent(proxy.Host, int32(proxy.Port)); err != nil {
+		//TODO telepathy service signal the download error
+		log.Print("Download issues: ", err)
+		return
+	} else {
+		storage.UpdateDownloaded(mNotificationInd.UUID, filePath)
+	}
+
+	mediator.NewMRetrieveConfFile <- mNotificationInd.UUID
+}
+
+func (mediator *Mediator) handleMRetrieveConf(uuid string) {
+	var filePath string
+	if f, err := storage.GetMMS(uuid); err == nil {
+		filePath = f
+	} else {
+		log.Print("Unable to retrieve MMS: ", err)
+		return
+	}
+	mmsData, err := ioutil.ReadFile(filePath)
+	if err != nil {
+		log.Print("Issues while reading from downloaded file: ", err)
+		return
+	}
+	mRetrieveConf := mms.NewMRetrieveConf(uuid)
+	dec := mms.NewDecoder(mmsData)
+	if err := dec.Decode(mRetrieveConf); err != nil {
+		log.Println("Unable to decode m-retrieve.conf: ", err, "with log", dec.GetLog())
+		return
+	}
+	mediator.NewMRetrieveConf <- mRetrieveConf
+	if mediator.telepathyService != nil {
+		if err := mediator.telepathyService.IncomingMessageAdded(mRetrieveConf); err != nil {
+			log.Println("Cannot notify telepathy-ofono about new message", err)
+		}
+	} else {
+		log.Print("Not sending recently retrieved message")
+	}
+}
+
+func (mediator *Mediator) handleRetrieved(mRetrieveConf *mms.MRetrieveConf) {
+	mNotifyRespInd := mRetrieveConf.NewMNotifyRespInd(useDeliveryReports)
+	if err := storage.UpdateRetrieved(mNotifyRespInd.UUID); err != nil {
+		log.Print("Can't update mms status: ", err)
+		return
+	}
+	mediator.NewMNotifyRespInd <- mNotifyRespInd
+}
+
+func (mediator *Mediator) handleMNotifyRespInd(mNotifyRespInd *mms.MNotifyRespInd) {
+	f, err := storage.CreateResponseFile(mNotifyRespInd.UUID)
+	if err != nil {
+		log.Print("Unable to create m-notifyresp.ind file for ", mNotifyRespInd.UUID)
+		return
+	}
+	enc := mms.NewEncoder(f)
+	if err := enc.Encode(mNotifyRespInd); err != nil {
+		log.Print("Unable to encode m-notifyresp.ind for ", mNotifyRespInd.UUID)
+		f.Close()
+		return
+	}
+	filePath := f.Name()
+	if err := f.Sync(); err != nil {
+		log.Print("Error while syncing", f.Name(), ": ", err)
+		return
+	}
+	if err := f.Close(); err != nil {
+		log.Print("Error while closing", f.Name(), ": ", err)
+		return
+	}
+	log.Printf("Created %s to handle m-notifyresp.ind for %s", filePath, mNotifyRespInd.UUID)
+	mediator.NewMNotifyRespIndFile <- filePath
+}
+
+func (mediator *Mediator) sendMNotifyRespInd(mNotifyRespIndFile string) {
+	defer os.Remove(mNotifyRespIndFile)
+	if _, err := mediator.uploadFile(mNotifyRespIndFile); err != nil {
+		log.Printf("Cannot upload m-notifyresp.ind encoded file %s to message center: %s", mNotifyRespIndFile, err)
+	}
+}
+
+func (mediator *Mediator) handleOutgoingMessage(msg *telepathy.OutgoingMessage) {
+	var cts []*mms.Attachment
+	for _, att := range msg.Attachments {
+		ct, err := mms.NewAttachment(att.Id, att.ContentType, att.FilePath)
+		if err != nil {
+			log.Print(err)
+			//TODO reply to telepathy ofono with an error
+			return
+		}
+		cts = append(cts, ct)
+	}
+	mSendReq := mms.NewMSendReq(msg.Recipients, cts, useDeliveryReports)
+	if _, err := mediator.telepathyService.ReplySendMessage(msg.Reply, mSendReq.UUID); err != nil {
+		log.Print(err)
+		return
+	}
+	mediator.NewMSendReq <- mSendReq
+}
+
+func (mediator *Mediator) handleMSendReq(mSendReq *mms.MSendReq) {
+	log.Print("Encoding M-Send.Req")
+	f, err := storage.CreateSendFile(mSendReq.UUID)
+	if err != nil {
+		log.Print("Unable to create m-send.req file for ", mSendReq.UUID)
+		return
+	}
+	defer f.Close()
+	enc := mms.NewEncoder(f)
+	if err := enc.Encode(mSendReq); err != nil {
+		log.Print("Unable to encode m-send.req for ", mSendReq.UUID)
+		if err := mediator.telepathyService.MessageStatusChanged(mSendReq.UUID, telepathy.PERMANENT_ERROR); err != nil {
+			log.Println(err)
+		}
+		f.Close()
+		return
+	}
+	filePath := f.Name()
+	if err := f.Sync(); err != nil {
+		log.Print("Error while syncing", f.Name(), ": ", err)
+		return
+	}
+	if err := f.Close(); err != nil {
+		log.Print("Error while closing", f.Name(), ": ", err)
+		return
+	}
+	log.Printf("Created %s to handle m-send.req for %s", filePath, mSendReq.UUID)
+	mediator.sendMSendReq(filePath, mSendReq.UUID)
+}
+
+func (mediator *Mediator) sendMSendReq(mSendReqFile, uuid string) {
+	defer os.Remove(mSendReqFile)
+	defer mediator.telepathyService.MessageDestroy(uuid)
+	mSendConfFile, err := mediator.uploadFile(mSendReqFile)
+	if err != nil {
+		if err := mediator.telepathyService.MessageStatusChanged(uuid, telepathy.TRANSIENT_ERROR); err != nil {
+			log.Println(err)
+		}
+		log.Printf("Cannot upload m-send.req encoded file %s to message center: %s", mSendReqFile, err)
+		return
+	}
+
+	defer os.Remove(mSendConfFile)
+	mSendConf, err := parseMSendConfFile(mSendConfFile)
+	if err != nil {
+		log.Println("Error while decoding m-send.conf:", err)
+		if err := mediator.telepathyService.MessageStatusChanged(uuid, telepathy.TRANSIENT_ERROR); err != nil {
+			log.Println(err)
+		}
+		return
+	}
+
+	log.Println("m-send.conf ResponseStatus for", uuid, "is", mSendConf.ResponseStatus)
+	var status string
+	switch mSendConf.Status() {
+	case nil:
+		status = telepathy.SENT
+	case mms.ErrPermanent:
+		status = telepathy.PERMANENT_ERROR
+	case mms.ErrTransient:
+		status = telepathy.TRANSIENT_ERROR
+	}
+	if err := mediator.telepathyService.MessageStatusChanged(uuid, status); err != nil {
+		log.Println(err)
+	}
+}
+
+func parseMSendConfFile(mSendConfFile string) (*mms.MSendConf, error) {
+	b, err := ioutil.ReadFile(mSendConfFile)
+	if err != nil {
+		return nil, err
+	}
+
+	mSendConf := mms.NewMSendConf()
+
+	dec := mms.NewDecoder(b)
+	if err := dec.Decode(mSendConf); err != nil {
+		return nil, err
+	}
+	return mSendConf, nil
+}
+
+func (mediator *Mediator) uploadFile(filePath string) (string, error) {
+	mediator.contextLock.Lock()
+	defer mediator.contextLock.Unlock()
+
+	preferredContext, _ := mediator.telepathyService.GetPreferredContext()
+	mmsContext, err := mediator.modem.ActivateMMSContext(preferredContext)
+	if err != nil {
+		return "", err
+	}
+	if err := mediator.telepathyService.SetPreferredContext(mmsContext.ObjectPath); err != nil {
+		log.Println("Unable to store the preferred context for MMS:", err)
+	}
+	defer func() {
+		if err := mediator.modem.DeactivateMMSContext(mmsContext); err != nil {
+			log.Println("Issues while deactivating context:", err)
+		}
+	}()
+
+	proxy, err := mmsContext.GetProxy()
+	if err != nil {
+		return "", err
+	}
+	msc, err := mmsContext.GetMessageCenter()
+	if err != nil {
+		return "", err
+	}
+	mSendRespFile, uploadErr := mms.Upload(filePath, msc, proxy.Host, int32(proxy.Port))
+
+	return mSendRespFile, uploadErr
+}

=== added directory 'mms'
=== renamed directory 'mms' => 'mms.moved'
=== added file 'mms/attachments.go'
--- mms/attachments.go	1970-01-01 00:00:00 +0000
+++ mms/attachments.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,295 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of mms.
+ *
+ * mms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * mms 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package mms
+
+import (
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"reflect"
+	"strings"
+)
+
+type Attachment struct {
+	MediaType        string
+	Type             string `encode:"no"`
+	Name             string `encode:"no"`
+	FileName         string `encode:"no"`
+	Charset          string `encode:"no"`
+	Start            string `encode:"no"`
+	StartInfo        string `encode:"no"`
+	Domain           string `encode:"no"`
+	Path             string `encode:"no"`
+	Comment          string `encode:"no"`
+	ContentLocation  string
+	ContentId        string
+	Level            byte    `encode:"no"`
+	Length           uint64  `encode:"no"`
+	Size             uint64  `encode:"no"`
+	CreationDate     uint64  `encode:"no"`
+	ModificationDate uint64  `encode:"no"`
+	ReadDate         uint64  `encode:"no"`
+	Offset           int     `encode:"no"`
+	Secure           bool    `encode:"no"`
+	Q                float64 `encode:"no"`
+	Data             []byte  `encode:"no"`
+}
+
+func NewAttachment(id, contentType, filePath string) (*Attachment, error) {
+	data, err := ioutil.ReadFile(filePath)
+	if err != nil {
+		return nil, fmt.Errorf("cannot create new ContentType for %s of content type %s on %s: %s", id, contentType, filePath, err)
+	}
+
+	ct := &Attachment{
+		ContentId:       id,
+		ContentLocation: id,
+		Name:            id,
+		Data:            data,
+	}
+
+	parts := strings.Split(contentType, ";")
+	ct.MediaType = strings.TrimSpace(parts[0])
+	for i := 1; i < len(parts); i++ {
+		if field := strings.Split(strings.TrimSpace(parts[i]), "="); len(field) > 1 {
+			switch strings.TrimSpace(field[0]) {
+			case "charset":
+				ct.Charset = strings.TrimSpace(field[1])
+			default:
+				log.Println("Unhandled field in attachment", field[0])
+			}
+		}
+	}
+
+	if contentType == "application/smil" {
+		start, err := getSmilStart(data)
+		if err != nil {
+			return nil, err
+		}
+		ct.ContentId = start
+	}
+	return ct, nil
+}
+
+func getSmilStart(smilData []byte) (string, error) {
+	smilStart := string(smilData)
+
+	i := strings.Index(smilStart, ">")
+	if i == -1 {
+		return "", errors.New("cannot find the SMIL Start tag")
+	} else if i+1 > len(smilData) {
+		return "", errors.New("buffer overrun while searching for the SMIL Start tag")
+	}
+	return smilStart[:i+1], nil
+}
+
+//GetSmil returns the text corresponding to the ContentType that holds the SMIL
+func (pdu *MRetrieveConf) GetSmil() (string, error) {
+	for i := range pdu.Attachments {
+		if strings.HasPrefix(pdu.Attachments[i].MediaType, "application/smil") {
+			return string(pdu.Attachments[i].Data), nil
+		}
+	}
+	return "", errors.New("cannot find SMIL data part")
+}
+
+//GetDataParts returns the non SMIL ContentType data parts
+func (pdu *MRetrieveConf) GetDataParts() []Attachment {
+	var dataParts []Attachment
+	for i := range pdu.Attachments {
+		if pdu.Attachments[i].MediaType == "application/smil" {
+			continue
+		}
+		dataParts = append(dataParts, pdu.Attachments[i])
+	}
+	return dataParts
+}
+
+func (dec *MMSDecoder) ReadContentTypeParts(reflectedPdu *reflect.Value) error {
+	var err error
+	var parts uint64
+	if parts, err = dec.ReadUintVar(nil, ""); err != nil {
+		return err
+	}
+	var dataParts []Attachment
+	dec.log = dec.log + fmt.Sprintf("Number of parts: %d\n", parts)
+	for i := uint64(0); i < parts; i++ {
+		headerLen, err := dec.ReadUintVar(nil, "")
+		if err != nil {
+			return err
+		}
+		dataLen, err := dec.ReadUintVar(nil, "")
+		if err != nil {
+			return err
+		}
+		headerEnd := dec.Offset + int(headerLen)
+		dec.log = dec.log + fmt.Sprintf("Attachament len(header): %d - len(data) %d\n", headerLen, dataLen)
+		var ct Attachment
+		ct.Offset = headerEnd + 1
+		ctReflected := reflect.ValueOf(&ct).Elem()
+		if err := dec.ReadContentType(&ctReflected); err == nil {
+			if err := dec.ReadMMSHeaders(&ctReflected, headerEnd); err != nil {
+				return err
+			}
+		} else if err != nil && err.Error() != "WAP message" { //TODO create error type
+			return err
+		}
+		dec.Offset = headerEnd + 1
+		if _, err := dec.ReadBoundedBytes(&ctReflected, "Data", dec.Offset+int(dataLen)); err != nil {
+			return err
+		}
+		if ct.MediaType == "application/smil" || strings.HasPrefix(ct.MediaType, "text/plain") || ct.MediaType == "" {
+			dec.log = dec.log + fmt.Sprintf("%s\n", ct.Data)
+		}
+		if ct.Charset != "" {
+			ct.MediaType = ct.MediaType + ";charset=" + ct.Charset
+		}
+		dataParts = append(dataParts, ct)
+	}
+	dataPartsR := reflect.ValueOf(dataParts)
+	reflectedPdu.FieldByName("Attachments").Set(dataPartsR)
+
+	return nil
+}
+
+func (dec *MMSDecoder) ReadMMSHeaders(ctMember *reflect.Value, headerEnd int) error {
+	for dec.Offset < headerEnd {
+		var err error
+		param, _ := dec.ReadInteger(nil, "")
+		switch param {
+		case MMS_PART_CONTENT_LOCATION:
+			_, err = dec.ReadString(ctMember, "ContentLocation")
+		case MMS_PART_CONTENT_ID:
+			_, err = dec.ReadString(ctMember, "ContentId")
+		default:
+			break
+		}
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (dec *MMSDecoder) ReadContentType(ctMember *reflect.Value) error {
+	if dec.Offset+1 >= len(dec.Data) {
+		return fmt.Errorf("message ended prematurely, offset: %d and payload length is %d", dec.Offset, len(dec.Data))
+	}
+	// These call the same function
+	if next := dec.Data[dec.Offset+1]; next&SHORT_FILTER != 0 {
+		return dec.ReadMediaType(ctMember, "MediaType")
+	} else if next >= TEXT_MIN && next <= TEXT_MAX {
+		return dec.ReadMediaType(ctMember, "MediaType")
+	}
+
+	var err error
+	var length uint64
+	if length, err = dec.ReadLength(ctMember); err != nil {
+		return err
+	}
+	dec.log = dec.log + fmt.Sprintf("Content Type Length: %d\n", length)
+	endOffset := int(length) + dec.Offset
+
+	if err := dec.ReadMediaType(ctMember, "MediaType"); err != nil {
+		return err
+	}
+
+	for dec.Offset < len(dec.Data) && dec.Offset < endOffset {
+		param, _ := dec.ReadInteger(nil, "")
+		switch param {
+		case WSP_PARAMETER_TYPE_Q:
+			err = dec.ReadQ(ctMember)
+		case WSP_PARAMETER_TYPE_CHARSET:
+			_, err = dec.ReadCharset(ctMember, "Charset")
+		case WSP_PARAMETER_TYPE_LEVEL:
+			_, err = dec.ReadShortInteger(ctMember, "Level")
+		case WSP_PARAMETER_TYPE_TYPE:
+			_, err = dec.ReadInteger(ctMember, "Type")
+		case WSP_PARAMETER_TYPE_NAME_DEFUNCT:
+			log.Println("Using deprecated Name header")
+			_, err = dec.ReadString(ctMember, "Name")
+		case WSP_PARAMETER_TYPE_FILENAME_DEFUNCT:
+			log.Println("Using deprecated FileName header")
+			_, err = dec.ReadString(ctMember, "FileName")
+		case WSP_PARAMETER_TYPE_DIFFERENCES:
+			err = errors.New("Unhandled Differences")
+		case WSP_PARAMETER_TYPE_PADDING:
+			dec.ReadShortInteger(nil, "")
+		case WSP_PARAMETER_TYPE_CONTENT_TYPE:
+			_, err = dec.ReadString(ctMember, "Type")
+		case WSP_PARAMETER_TYPE_START_DEFUNCT:
+			log.Println("Using deprecated Start header")
+			_, err = dec.ReadString(ctMember, "Start")
+		case WSP_PARAMETER_TYPE_START_INFO_DEFUNCT:
+			log.Println("Using deprecated StartInfo header")
+			_, err = dec.ReadString(ctMember, "StartInfo")
+		case WSP_PARAMETER_TYPE_COMMENT_DEFUNCT:
+			log.Println("Using deprecated Comment header")
+			_, err = dec.ReadString(ctMember, "Comment")
+		case WSP_PARAMETER_TYPE_DOMAIN_DEFUNCT:
+			log.Println("Using deprecated Domain header")
+			_, err = dec.ReadString(ctMember, "Domain")
+		case WSP_PARAMETER_TYPE_MAX_AGE:
+			err = errors.New("Unhandled Max Age")
+		case WSP_PARAMETER_TYPE_PATH_DEFUNCT:
+			log.Println("Using deprecated Path header")
+			_, err = dec.ReadString(ctMember, "Path")
+		case WSP_PARAMETER_TYPE_SECURE:
+			log.Println("Unhandled Secure header detected")
+		case WSP_PARAMETER_TYPE_SEC:
+			v, _ := dec.ReadShortInteger(nil, "")
+			log.Println("Using deprecated and unhandled Sec header with value", v)
+		case WSP_PARAMETER_TYPE_MAC:
+			err = errors.New("Unhandled MAC")
+		case WSP_PARAMETER_TYPE_CREATION_DATE:
+		case WSP_PARAMETER_TYPE_MODIFICATION_DATE:
+		case WSP_PARAMETER_TYPE_READ_DATE:
+			err = errors.New("Unhandled Date parameters")
+		case WSP_PARAMETER_TYPE_SIZE:
+			_, err = dec.ReadInteger(ctMember, "Size")
+		case WSP_PARAMETER_TYPE_NAME:
+			_, err = dec.ReadString(ctMember, "Name")
+		case WSP_PARAMETER_TYPE_FILENAME:
+			_, err = dec.ReadString(ctMember, "FileName")
+		case WSP_PARAMETER_TYPE_START:
+			_, err = dec.ReadString(ctMember, "Start")
+		case WSP_PARAMETER_TYPE_START_INFO:
+			_, err = dec.ReadString(ctMember, "StartInfo")
+		case WSP_PARAMETER_TYPE_COMMENT:
+			_, err = dec.ReadString(ctMember, "Comment")
+		case WSP_PARAMETER_TYPE_DOMAIN:
+			_, err = dec.ReadString(ctMember, "Domain")
+		case WSP_PARAMETER_TYPE_PATH:
+			_, err = dec.ReadString(ctMember, "Path")
+		case WSP_PARAMETER_TYPE_UNTYPED:
+			v, _ := dec.ReadString(nil, "")
+			log.Println("Unhandled Secure header detected with value", v)
+		default:
+			err = fmt.Errorf("Unhandled parameter %#x == %d at offset %d", param, param, dec.Offset)
+		}
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}

=== added directory 'mms/decode-cli'
=== added file 'mms/decode-cli/decode.go'
--- mms/decode-cli/decode.go	1970-01-01 00:00:00 +0000
+++ mms/decode-cli/decode.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,70 @@
+package main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+
+	"launchpad.net/nuntium/mms"
+)
+
+func main() {
+	var targetPath string
+	if len(os.Args) < 2 {
+		usage()
+	} else if len(os.Args) == 3 {
+		targetPath = os.Args[2]
+	} else if len(os.Args) > 3 {
+		usage()
+	}
+
+	mmsFile := os.Args[1]
+	if _, err := os.Stat(mmsFile); os.IsNotExist(err) {
+		fmt.Printf("File argument %s does no exist\n", mmsFile)
+		os.Exit(1)
+	}
+
+	mmsData, err := ioutil.ReadFile(mmsFile)
+	if err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+
+	retConfHdr := mms.NewMRetrieveConf(mmsFile)
+	dec := mms.NewDecoder(mmsData)
+	if err := dec.Decode(retConfHdr); err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+
+	if targetPath != "" {
+		fmt.Println("Saving to", targetPath)
+		writeParts(targetPath, retConfHdr.Attachments)
+	}
+
+	fmt.Println(dec.GetLog())
+}
+
+func usage() {
+	fmt.Printf("Usage: %s [mms] [decode dir]\n", os.Args[0])
+	os.Exit(1)
+}
+
+func writeParts(targetPath string, parts []mms.Attachment) {
+	if fi, err := os.Stat(targetPath); err != nil {
+		if err := os.MkdirAll(targetPath, 0755); err != nil {
+			fmt.Println(err)
+		}
+	} else if !fi.IsDir() {
+		fmt.Println(targetPath, "is not a directory")
+		os.Exit(1)
+	}
+
+	for i, _ := range parts {
+		if parts[i].Name != "" {
+			ioutil.WriteFile(filepath.Join(targetPath, parts[i].Name), parts[i].Data, 0644)
+		}
+		fmt.Println(parts[i].MediaType, parts[i].Name)
+	}
+}

=== added file 'mms/decoder.go'
--- mms/decoder.go	1970-01-01 00:00:00 +0000
+++ mms/decoder.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,437 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of mms.
+ *
+ * mms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * mms 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package mms
+
+import (
+	"fmt"
+	"reflect"
+)
+
+func NewDecoder(data []byte) *MMSDecoder {
+	return &MMSDecoder{Data: data}
+}
+
+type MMSDecoder struct {
+	Data   []byte
+	Offset int
+	log    string
+}
+
+func (dec *MMSDecoder) ReadEncodedString(reflectedPdu *reflect.Value, hdr string) (string, error) {
+	var length uint64
+	var err error
+	switch {
+	case dec.Data[dec.Offset+1] < SHORT_LENGTH_MAX:
+		var l byte
+		l, err = dec.ReadShortInteger(nil, "")
+		length = uint64(l)
+	case dec.Data[dec.Offset+1] == LENGTH_QUOTE:
+		dec.Offset++
+		length, err = dec.ReadUintVar(nil, "")
+	}
+	if err != nil {
+		return "", err
+	}
+	if length != 0 {
+		charset, err := dec.ReadCharset(nil, "")
+		if err != nil {
+			return "", err
+		}
+		dec.log = dec.log + fmt.Sprintf("Next string encoded with: %s\n", charset)
+	}
+	var str string
+	if str, err = dec.ReadString(reflectedPdu, hdr); err != nil {
+		return "", err
+	}
+	return str, nil
+}
+
+func (dec *MMSDecoder) ReadQ(reflectedPdu *reflect.Value) error {
+	v, err := dec.ReadUintVar(nil, "")
+	if err != nil {
+		return err
+	}
+	q := float64(v)
+	if q > 100 {
+		q = (q - 100) / 1000
+	} else {
+		q = (q - 1) / 100
+	}
+	reflectedPdu.FieldByName("Q").SetFloat(q)
+	return nil
+}
+
+// ReadLength reads the length from the next position according to section
+// 8.4.2.2 of WAP-230-WSP-20010705-a.
+//
+// Value-length = Short-length | (Length-quote Length)
+// ; Value length is used to indicate the length of the value to follow
+// Short-length = <Any octet 0-30> (0x7f to check for short)
+// Length-quote = <Octet 31>
+// Length = Uintvar-integer
+func (dec *MMSDecoder) ReadLength(reflectedPdu *reflect.Value) (length uint64, err error) {
+	switch {
+	case dec.Data[dec.Offset+1]&0x7f <= SHORT_LENGTH_MAX:
+		l, err := dec.ReadShortInteger(nil, "")
+		v := uint64(l)
+		if reflectedPdu != nil {
+			reflectedPdu.FieldByName("Length").SetUint(v)
+		}
+		return v, err
+	case dec.Data[dec.Offset+1] == LENGTH_QUOTE:
+		dec.Offset++
+		var hdr string
+		if reflectedPdu != nil {
+			hdr = "Length"
+		}
+		return dec.ReadUintVar(reflectedPdu, hdr)
+	}
+	return 0, fmt.Errorf("Unhandled length %#x @%d", dec.Data[dec.Offset+1], dec.Offset)
+}
+
+func (dec *MMSDecoder) ReadCharset(reflectedPdu *reflect.Value, hdr string) (string, error) {
+	var charset string
+
+	if dec.Data[dec.Offset] == ANY_CHARSET {
+		dec.Offset++
+		charset = "*"
+	} else {
+		charCode, err := dec.ReadInteger(nil, "")
+		if err != nil {
+			return "", err
+		}
+		var ok bool
+		if charset, ok = CHARSETS[charCode]; !ok {
+			return "", fmt.Errorf("Cannot find matching charset for %#x == %d", charCode, charCode)
+		}
+	}
+	if hdr != "" {
+		reflectedPdu.FieldByName("Charset").SetString(charset)
+	}
+	return charset, nil
+}
+
+func (dec *MMSDecoder) ReadMediaType(reflectedPdu *reflect.Value, hdr string) (err error) {
+	var mediaType string
+	origOffset := dec.Offset
+	if dec.Data[dec.Offset+1] >= TEXT_MIN && dec.Data[dec.Offset+1] <= TEXT_MAX {
+		if mediaType, err = dec.ReadString(nil, ""); err != nil {
+			return err
+		}
+	} else if mt, err := dec.ReadInteger(nil, ""); err == nil && len(CONTENT_TYPES) > int(mt) {
+		mediaType = CONTENT_TYPES[mt]
+	} else {
+		return fmt.Errorf("cannot decode media type for field beginning with %#x@%d", dec.Data[origOffset], origOffset)
+	}
+
+	reflectedPdu.FieldByName(hdr).SetString(mediaType)
+	dec.log = dec.log + fmt.Sprintf("%s: %s\n", hdr, mediaType)
+	return nil
+}
+
+func (dec *MMSDecoder) ReadTo(reflectedPdu *reflect.Value) error {
+	toField, err := dec.ReadEncodedString(reflectedPdu, "")
+	if err != nil {
+		return err
+	}
+	to := reflectedPdu.FieldByName("To").String()
+	if to != "" {
+		toField = toField + "," + to
+	}
+	reflectedPdu.FieldByName("To").SetString(toField)
+	return err
+}
+
+func (dec *MMSDecoder) ReadString(reflectedPdu *reflect.Value, hdr string) (string, error) {
+	dec.Offset++
+	if dec.Data[dec.Offset] == 34 { // Skip the quote char(34) == "
+		dec.Offset++
+	}
+	begin := dec.Offset
+	for ; len(dec.Data) > dec.Offset; dec.Offset++ {
+		if dec.Data[dec.Offset] == 0 {
+			break
+		}
+	}
+	if len(dec.Data) == dec.Offset {
+		return "", fmt.Errorf("reached end of data while trying to read string: %s", dec.Data[begin:])
+	}
+	v := string(dec.Data[begin:dec.Offset])
+	if hdr != "" {
+		reflectedPdu.FieldByName(hdr).SetString(v)
+		dec.log = dec.log + fmt.Sprintf("Setting %s to %s\n", hdr, v)
+	}
+	return v, nil
+}
+
+func (dec *MMSDecoder) ReadShortInteger(reflectedPdu *reflect.Value, hdr string) (byte, error) {
+	dec.Offset++
+	/*
+		TODO fix use of short when not short
+		if dec.Data[dec.Offset] & 0x80 == 0 {
+			return 0, fmt.Errorf("Data on offset %d with value %#x is not a short integer", dec.Offset, dec.Data[dec.Offset])
+		}
+	*/
+	v := dec.Data[dec.Offset] & 0x7F
+	if hdr != "" {
+		reflectedPdu.FieldByName(hdr).SetUint(uint64(v))
+		dec.log = dec.log + fmt.Sprintf("Setting %s to %#x == %d\n", hdr, v, v)
+	}
+	return v, nil
+}
+
+func (dec *MMSDecoder) ReadByte(reflectedPdu *reflect.Value, hdr string) (byte, error) {
+	dec.Offset++
+	v := dec.Data[dec.Offset]
+	if hdr != "" {
+		reflectedPdu.FieldByName(hdr).SetUint(uint64(v))
+		dec.log = dec.log + fmt.Sprintf("Setting %s to %#x == %d\n", hdr, v, v)
+	}
+	return v, nil
+}
+
+func (dec *MMSDecoder) ReadBytes(reflectedPdu *reflect.Value, hdr string) ([]byte, error) {
+	dec.Offset++
+	v := []byte(dec.Data[dec.Offset:])
+	if hdr != "" {
+		reflectedPdu.FieldByName(hdr).SetBytes(v)
+		dec.log = dec.log + fmt.Sprintf("Setting %s to %#x == %d\n", hdr, v, v)
+	}
+	return v, nil
+}
+
+func (dec *MMSDecoder) ReadBoundedBytes(reflectedPdu *reflect.Value, hdr string, end int) ([]byte, error) {
+	v := []byte(dec.Data[dec.Offset:end])
+	if hdr != "" {
+		reflectedPdu.FieldByName(hdr).SetBytes(v)
+	}
+	dec.Offset = end - 1
+	return v, nil
+}
+
+// A UintVar is a variable lenght uint of up to 5 octects long where
+// more octects available are indicated with the most significant bit
+// set to 1
+func (dec *MMSDecoder) ReadUintVar(reflectedPdu *reflect.Value, hdr string) (value uint64, err error) {
+	dec.Offset++
+	for dec.Data[dec.Offset]>>7 == 0x01 {
+		value = value << 7
+		value |= uint64(dec.Data[dec.Offset] & 0x7F)
+		dec.Offset++
+	}
+
+	value = value << 7
+	value |= uint64(dec.Data[dec.Offset] & 0x7F)
+	if hdr != "" {
+		reflectedPdu.FieldByName(hdr).SetUint(value)
+		dec.log = dec.log + fmt.Sprintf("Setting %s to %d\n", hdr, value)
+	}
+	return value, nil
+}
+
+func (dec *MMSDecoder) ReadInteger(reflectedPdu *reflect.Value, hdr string) (uint64, error) {
+	param := dec.Data[dec.Offset+1]
+	var v uint64
+	var err error
+	switch {
+	case param&0x80 != 0:
+		var vv byte
+		vv, err = dec.ReadShortInteger(nil, "")
+		v = uint64(vv)
+	default:
+		v, err = dec.ReadLongInteger(nil, "")
+	}
+	if hdr != "" {
+		reflectedPdu.FieldByName(hdr).SetUint(v)
+		dec.log = dec.log + fmt.Sprintf("Setting %s to %d\n", hdr, v)
+	}
+	return v, err
+}
+
+func (dec *MMSDecoder) ReadLongInteger(reflectedPdu *reflect.Value, hdr string) (uint64, error) {
+	dec.Offset++
+	size := int(dec.Data[dec.Offset])
+	if size > SHORT_LENGTH_MAX {
+		return 0, fmt.Errorf("cannot encode long integer, lenght was %d but expected %d", size, SHORT_LENGTH_MAX)
+	}
+	dec.Offset++
+	end := dec.Offset + size
+	var v uint64
+	for ; dec.Offset < end; dec.Offset++ {
+		v = v << 8
+		v |= uint64(dec.Data[dec.Offset])
+	}
+	dec.Offset--
+	if hdr != "" {
+		reflectedPdu.FieldByName(hdr).SetUint(uint64(v))
+		dec.log = dec.log + fmt.Sprintf("Setting %s to %d\n", hdr, v)
+	}
+	return v, nil
+}
+
+//getParam reads the next parameter to decode and returns it if it's well known
+//or just decodes and discards if it's application specific, if the latter is
+//the case it also returns false
+func (dec *MMSDecoder) getParam() (byte, bool, error) {
+	if dec.Data[dec.Offset]&0x80 != 0 {
+		return dec.Data[dec.Offset] & 0x7f, true, nil
+	} else {
+		var param, value string
+		var err error
+		dec.Offset--
+		//Read the parameter name
+		if param, err = dec.ReadString(nil, ""); err != nil {
+			return 0, false, err
+		}
+		//Read the parameter value
+		if value, err = dec.ReadString(nil, ""); err != nil {
+			return 0, false, err
+		}
+		dec.log = dec.log + fmt.Sprintf("Ignoring application header: %#x: %s", param, value)
+		return 0, false, nil
+	}
+}
+
+func (dec *MMSDecoder) Decode(pdu MMSReader) (err error) {
+	reflectedPdu := reflect.ValueOf(pdu).Elem()
+	moreHdrToRead := true
+	//fmt.Printf("len data: %d, data: %x\n", len(dec.Data), dec.Data)
+	for ; (dec.Offset < len(dec.Data)) && moreHdrToRead; dec.Offset++ {
+		//fmt.Printf("offset %d, value: %x\n", dec.Offset, dec.Data[dec.Offset])
+		err = nil
+		param, needsDecoding, err := dec.getParam()
+		if err != nil {
+			return err
+		} else if !needsDecoding {
+			continue
+		}
+		switch param {
+		case X_MMS_MESSAGE_TYPE:
+			dec.Offset++
+			expectedType := byte(reflectedPdu.FieldByName("Type").Uint())
+			parsedType := dec.Data[dec.Offset]
+			//Unknown message types will be discarded. OMA-WAP-MMS-ENC-v1.1 section 7.2.16
+			if parsedType != expectedType {
+				err = fmt.Errorf("Expected message type %x got %x", expectedType, parsedType)
+			}
+		case FROM:
+			dec.Offset++
+			size := int(dec.Data[dec.Offset])
+			dec.Offset++
+			token := dec.Data[dec.Offset]
+			switch token {
+			case TOKEN_INSERT_ADDRESS:
+				break
+			case TOKEN_ADDRESS_PRESENT:
+				// TODO add check for /TYPE=PLMN
+				var from string
+				from, err = dec.ReadString(&reflectedPdu, "From")
+				// size - 2 == size - token - '0'
+				if len(from) != size-2 {
+					err = fmt.Errorf("From field is %d but expected size is %d", len(from), size-2)
+				}
+			default:
+				err = fmt.Errorf("Unhandled token address in from field %x", token)
+			}
+		case X_MMS_EXPIRY:
+			dec.Offset++
+			size := int(dec.Data[dec.Offset])
+			dec.Offset++
+			token := dec.Data[dec.Offset]
+			dec.Offset++
+			var val uint
+			endOffset := dec.Offset + size - 2
+			for ; dec.Offset < endOffset; dec.Offset++ {
+				val = (val << 8) | uint(dec.Data[dec.Offset])
+			}
+			// TODO add switch case for token
+			dec.log = dec.log + fmt.Sprintf("Expiry token: %x\n", token)
+			reflectedPdu.FieldByName("Expiry").SetUint(uint64(val))
+			dec.log = dec.log + fmt.Sprintf("Message Expiry %d, %x\n", val, dec.Data[dec.Offset])
+		case X_MMS_TRANSACTION_ID:
+			_, err = dec.ReadString(&reflectedPdu, "TransactionId")
+		case CONTENT_TYPE:
+			ctMember := reflectedPdu.FieldByName("Content")
+			if err = dec.ReadContentType(&ctMember); err != nil {
+				return err
+			}
+			//application/vnd.wap.multipart.related and others
+			if ctMember.FieldByName("MediaType").String() != "text/plain" {
+				err = dec.ReadContentTypeParts(&reflectedPdu)
+			} else {
+				dec.Offset++
+				_, err = dec.ReadBoundedBytes(&reflectedPdu, "Data", len(dec.Data))
+			}
+			moreHdrToRead = false
+		case X_MMS_CONTENT_LOCATION:
+			_, err = dec.ReadString(&reflectedPdu, "ContentLocation")
+			moreHdrToRead = false
+		case MESSAGE_ID:
+			_, err = dec.ReadString(&reflectedPdu, "MessageId")
+		case SUBJECT:
+			_, err = dec.ReadEncodedString(&reflectedPdu, "Subject")
+		case TO:
+			err = dec.ReadTo(&reflectedPdu)
+		case CC:
+			_, err = dec.ReadEncodedString(&reflectedPdu, "Cc")
+		case X_MMS_REPLY_CHARGING_ID:
+			_, err = dec.ReadString(&reflectedPdu, "ReplyChargingId")
+		case X_MMS_RETRIEVE_TEXT:
+			_, err = dec.ReadString(&reflectedPdu, "RetrieveText")
+		case X_MMS_MMS_VERSION:
+			_, err = dec.ReadShortInteger(&reflectedPdu, "Version")
+		case X_MMS_MESSAGE_CLASS:
+			//TODO implement Token text form
+			_, err = dec.ReadByte(&reflectedPdu, "Class")
+		case X_MMS_REPLY_CHARGING:
+			_, err = dec.ReadByte(&reflectedPdu, "ReplyCharging")
+		case X_MMS_REPLY_CHARGING_DEADLINE:
+			_, err = dec.ReadByte(&reflectedPdu, "ReplyChargingDeadLine")
+		case X_MMS_PRIORITY:
+			_, err = dec.ReadByte(&reflectedPdu, "Priority")
+		case X_MMS_RETRIEVE_STATUS:
+			_, err = dec.ReadByte(&reflectedPdu, "RetrieveStatus")
+		case X_MMS_RESPONSE_STATUS:
+			_, err = dec.ReadByte(&reflectedPdu, "ResponseStatus")
+		case X_MMS_RESPONSE_TEXT:
+			_, err = dec.ReadString(&reflectedPdu, "ResponseText")
+		case X_MMS_DELIVERY_REPORT:
+			_, err = dec.ReadByte(&reflectedPdu, "DeliveryReport")
+		case X_MMS_READ_REPORT:
+			_, err = dec.ReadByte(&reflectedPdu, "ReadReport")
+		case X_MMS_MESSAGE_SIZE:
+			_, err = dec.ReadLongInteger(&reflectedPdu, "Size")
+		case DATE:
+			_, err = dec.ReadLongInteger(&reflectedPdu, "Date")
+		default:
+			return fmt.Errorf("Unhandled byte: %#0x\tdec: %d\tdec.Offset: %d ... decoded so far: %s", param, param, dec.Offset)
+		}
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (dec *MMSDecoder) GetLog() string {
+	return dec.log
+}

=== added file 'mms/decoder_payload_test.go'
--- mms/decoder_payload_test.go	1970-01-01 00:00:00 +0000
+++ mms/decoder_payload_test.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of mms.
+ *
+ * mms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * mms 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package mms
+
+import (
+	"io/ioutil"
+
+	. "launchpad.net/gocheck"
+)
+
+type PayloadDecoderTestSuite struct{}
+
+var _ = Suite(&PayloadDecoderTestSuite{})
+
+func (s *PayloadDecoderTestSuite) TestDecodeSuccessfulMSendConf(c *C) {
+	inputBytes, err := ioutil.ReadFile("test_payloads/m-send.conf_success")
+	c.Assert(err, IsNil)
+
+	mSendConf := NewMSendConf()
+	dec := NewDecoder(inputBytes)
+	err = dec.Decode(mSendConf)
+	c.Assert(err, IsNil)
+	c.Check(mSendConf.ResponseStatus, Equals, ResponseStatusOk)
+	c.Check(mSendConf.TransactionId, Equals, "ad6babe2628710c443cdeb3ff39679ac")
+}
+
+func (s *PayloadDecoderTestSuite) TestDecodeInvalidMSendConf(c *C) {
+	inputBytes := []byte(`<html><head><title>719</title><meta http-equiv="Cache-Control" content="max-age=0" /><meta http-equiv="Cache-control" content="no-cache" /></head><body><h3 align="center">Disculpe,ha ocurrido un error: Failure to Query from Radius Server</h3><br/><p>Por favor, regrese al menu anterior o acceda al siguiente link.<br/></p><ul><li><a href="http://wap.personal.com.ar/"><strong>Home Personal</strong></a></li></ul></body></html>^M`)
+
+	mSendConf := NewMSendConf()
+	dec := NewDecoder(inputBytes)
+	err := dec.Decode(mSendConf)
+	c.Check(err, NotNil)
+	c.Check(mSendConf.ResponseStatus, Equals, byte(0x0))
+	c.Check(mSendConf.TransactionId, Equals, "")
+	mSendConf.Status()
+}

=== added file 'mms/decoder_test.go'
--- mms/decoder_test.go	1970-01-01 00:00:00 +0000
+++ mms/decoder_test.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of mms.
+ *
+ * mms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * mms 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package mms
+
+import (
+	"errors"
+
+	. "launchpad.net/gocheck"
+)
+
+type DecoderTestSuite struct{}
+
+var _ = Suite(&DecoderTestSuite{})
+
+func (s *DecoderTestSuite) TestDecodeStringNoNullByteTerminator(c *C) {
+	inputBytes := []byte{
+		//stub byte
+		0x80,
+		//<html>
+		0x3c, 0x68, 0x74, 0x6d, 0x6c, 0x3e,
+	}
+	expectedErr := errors.New("reached end of data while trying to read string: <html>")
+	dec := NewDecoder(inputBytes)
+	str, err := dec.ReadString(nil, "")
+	c.Check(str, Equals, "")
+	c.Check(err, DeepEquals, expectedErr)
+}
+
+func (s *DecoderTestSuite) TestDecodeStringWithNullByteTerminator(c *C) {
+	inputBytes := []byte{
+		//stub byte
+		0x80,
+		//<smil>
+		0x3c, 0x73, 0x6d, 0x69, 0x6c, 0x3e, 0x00,
+	}
+	dec := NewDecoder(inputBytes)
+	str, err := dec.ReadString(nil, "")
+	c.Check(str, Equals, "<smil>")
+	c.Check(err, IsNil)
+}

=== added file 'mms/download.go'
--- mms/download.go	1970-01-01 00:00:00 +0000
+++ mms/download.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of mms.
+ *
+ * mms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * mms 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package mms
+
+import (
+	"errors"
+	"fmt"
+	"log"
+	"time"
+
+	"launchpad.net/udm"
+)
+
+func (pdu *MNotificationInd) DownloadContent(proxyHost string, proxyPort int32) (string, error) {
+	downloadManager, err := udm.NewDownloadManager()
+	if err != nil {
+		return "", err
+	}
+	download, err := downloadManager.CreateMmsDownload(pdu.ContentLocation, proxyHost, proxyPort)
+	if err != nil {
+		return "", err
+	}
+	f := download.Finished()
+	p := download.DownloadProgress()
+	e := download.Error()
+	log.Print("Starting download of ", pdu.ContentLocation, " with proxy ", proxyHost, ":", proxyPort)
+	download.Start()
+	for {
+		select {
+		case progress := <-p:
+			log.Print("Progress:", progress.Total, progress.Received)
+		case downloadFilePath := <-f:
+			log.Print("File downloaded to ", downloadFilePath)
+			return downloadFilePath, nil
+		case <-time.After(3 * time.Minute):
+			return "", fmt.Errorf("Download timeout exceeded while fetching %s", pdu.ContentLocation)
+		case err := <-e:
+			return "", err
+		}
+	}
+}
+
+func Upload(file, msc, proxyHost string, proxyPort int32) (string, error) {
+	udm, err := udm.NewUploadManager()
+	if err != nil {
+		return "", err
+	}
+	upload, err := udm.CreateMmsUpload(msc, file, proxyHost, proxyPort)
+	if err != nil {
+		return "", err
+	}
+	f := upload.Finished()
+	p := upload.UploadProgress()
+	e := upload.Error()
+	log.Print("Starting upload of ", file, " to ", msc, " with proxy ", proxyHost, ":", proxyPort)
+	if err := upload.Start(); err != nil {
+		return "", err
+	}
+
+	for {
+		select {
+		case progress := <-p:
+			log.Print("Progress:", progress.Total, progress.Received)
+		case responseFile := <-f:
+			log.Print("File ", responseFile, " returned in upload")
+			return responseFile, nil
+		case <-time.After(10 * time.Minute):
+			return "", errors.New("upload timeout")
+		case err := <-e:
+			return "", err
+		}
+	}
+}

=== added file 'mms/encode_decode_test.go'
--- mms/encode_decode_test.go	1970-01-01 00:00:00 +0000
+++ mms/encode_decode_test.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of mms.
+ *
+ * mms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * mms 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package mms
+
+import (
+	"bytes"
+
+	. "launchpad.net/gocheck"
+)
+
+type EncodeDecodeTestSuite struct {
+	bytes *bytes.Buffer
+	enc   *MMSEncoder
+	dec   *MMSDecoder
+}
+
+var _ = Suite(&EncodeDecodeTestSuite{})
+
+func (s *EncodeDecodeTestSuite) SetUpTest(c *C) {
+	s.bytes = new(bytes.Buffer)
+	s.enc = NewEncoder(s.bytes)
+	c.Assert(s.enc.writeByte(0), IsNil)
+}
+
+func (s *EncodeDecodeTestSuite) TestString(c *C) {
+	testStr := "'Hello World!"
+	c.Assert(s.enc.writeString(testStr), IsNil)
+	s.dec = NewDecoder(s.bytes.Bytes())
+
+	str, err := s.dec.ReadString(nil, "")
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, testStr)
+}
+
+func (s *EncodeDecodeTestSuite) TestByte(c *C) {
+	testBytes := []byte{0, 0x79, 0x80, 0x81}
+	for i := range testBytes {
+		c.Assert(s.enc.writeByte(testBytes[i]), IsNil)
+	}
+	bytes := s.bytes.Bytes()
+	s.dec = NewDecoder(bytes)
+	for i := range testBytes {
+		b, err := s.dec.ReadByte(nil, "")
+		c.Assert(err, IsNil)
+		c.Assert(b, Equals, testBytes[i], Commentf("From testBytes[%d] and encoded bytes: %#x", i, bytes))
+	}
+}
+
+func (s *EncodeDecodeTestSuite) TestInteger(c *C) {
+	// 128 bounds short and long integers
+	testInts := []uint64{512, 100, 127, 128, 129, 255, 256, 511, 3000}
+	for i := range testInts {
+		c.Assert(s.enc.writeInteger(testInts[i]), IsNil)
+	}
+	bytes := s.bytes.Bytes()
+	s.dec = NewDecoder(bytes)
+	for i := range testInts {
+		integer, err := s.dec.ReadInteger(nil, "")
+		c.Assert(err, IsNil)
+		c.Check(integer, Equals, testInts[i], Commentf("%d != %d with encoded bytes starting at %d: %d", integer, testInts[i], i, bytes))
+	}
+}
+
+func (s *EncodeDecodeTestSuite) TestUintVar(c *C) {
+	testInts := []uint64{127, 512, 255, 256, 3000}
+	for i := range testInts {
+		c.Assert(s.enc.writeUintVar(testInts[i]), IsNil)
+	}
+	bytes := s.bytes.Bytes()
+	s.dec = NewDecoder(bytes)
+	for i := range testInts {
+		integer, err := s.dec.ReadUintVar(nil, "")
+		c.Assert(err, IsNil)
+		c.Check(integer, Equals, testInts[i], Commentf("%d != %d with encoded bytes starting at %d: %d", integer, testInts[i], i, bytes))
+	}
+}
+
+func (s *EncodeDecodeTestSuite) TestLength(c *C) {
+	// > 30 requires encoding with length quote
+	testLengths := []uint64{10, 1, 29, 30, 31, 500}
+	for i := range testLengths {
+		c.Assert(s.enc.writeLength(testLengths[i]), IsNil)
+	}
+	bytes := s.bytes.Bytes()
+	s.dec = NewDecoder(bytes)
+	for i := range testLengths {
+		integer, err := s.dec.ReadLength(nil)
+		c.Assert(err, IsNil, Commentf("%d != %d with encoded bytes starting at %d: %d", integer, testLengths[i], s.dec.Offset, bytes))
+		c.Check(integer, Equals, testLengths[i], Commentf("%d != %d with encoded bytes starting at %d: %d", integer, testLengths[i], s.dec.Offset, bytes))
+	}
+}

=== added file 'mms/encoder.go'
--- mms/encoder.go	1970-01-01 00:00:00 +0000
+++ mms/encoder.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,448 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of mms.
+ *
+ * mms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * mms 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package mms
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"io"
+	"log"
+	"reflect"
+)
+
+type MMSEncoder struct {
+	w   io.Writer
+	log string
+}
+
+func NewEncoder(w io.Writer) *MMSEncoder {
+	return &MMSEncoder{w: w}
+}
+
+func (enc *MMSEncoder) Encode(pdu MMSWriter) error {
+	rPdu := reflect.ValueOf(pdu).Elem()
+
+	//The order of the following fields doens't matter much
+	typeOfPdu := rPdu.Type()
+	var err error
+	for i := 0; i < rPdu.NumField(); i++ {
+		fieldName := typeOfPdu.Field(i).Name
+		encodeTag := typeOfPdu.Field(i).Tag.Get("encode")
+		f := rPdu.Field(i)
+
+		if encodeTag == "no" {
+			continue
+		}
+		switch f.Kind() {
+		case reflect.Uint:
+		case reflect.Uint8:
+			enc.log = enc.log + fmt.Sprintf("%s: %d %#x\n", fieldName, f.Uint(), f.Uint())
+		case reflect.Bool:
+			enc.log = enc.log + fmt.Sprintf(fieldName, f.Bool())
+		default:
+			enc.log = enc.log + fmt.Sprintf(fieldName, f)
+		}
+
+		switch fieldName {
+		case "Type":
+			err = enc.writeByteParam(X_MMS_MESSAGE_TYPE, byte(f.Uint()))
+		case "Version":
+			err = enc.writeByteParam(X_MMS_MMS_VERSION, byte(f.Uint()))
+		case "TransactionId":
+			err = enc.writeStringParam(X_MMS_TRANSACTION_ID, f.String())
+		case "Status":
+			err = enc.writeByteParam(X_MMS_STATUS, byte(f.Uint()))
+		case "From":
+			err = enc.writeFrom()
+		case "Name":
+			err = enc.writeStringParam(WSP_PARAMETER_TYPE_NAME_DEFUNCT, f.String())
+		case "Start":
+			err = enc.writeStringParam(WSP_PARAMETER_TYPE_START_DEFUNCT, f.String())
+		case "To":
+			err = enc.writeStringParam(TO, f.String())
+		case "ContentType":
+			// if there is a ContentType there has to be content
+			if mSendReq, ok := pdu.(*MSendReq); ok {
+				if err := enc.setParam(CONTENT_TYPE); err != nil {
+					return err
+				}
+				if err = enc.writeContentType(mSendReq.ContentType, mSendReq.ContentTypeStart, mSendReq.ContentTypeType, ""); err != nil {
+					return err
+				}
+				err = enc.writeAttachments(mSendReq.Attachments)
+			} else {
+				err = errors.New("unhandled content type")
+			}
+		case "MediaType":
+			if a, ok := pdu.(*Attachment); ok {
+				if err = enc.writeContentType(a.MediaType, "", "", a.Name); err != nil {
+					return err
+				}
+			} else {
+				if err = enc.writeMediaType(f.String()); err != nil {
+					return err
+				}
+			}
+		case "Charset":
+			//TODO
+			err = enc.writeCharset(f.String())
+		case "ContentLocation":
+			err = enc.writeStringParam(MMS_PART_CONTENT_LOCATION, f.String())
+		case "ContentId":
+			err = enc.writeQuotedStringParam(MMS_PART_CONTENT_ID, f.String())
+		case "Date":
+			date := f.Uint()
+			if date > 0 {
+				err = enc.writeLongIntegerParam(DATE, date)
+			}
+		case "Class":
+			err = enc.writeByteParam(X_MMS_MESSAGE_CLASS, byte(f.Uint()))
+		case "ReportAllowed":
+			err = enc.writeByteParam(X_MMS_REPORT_ALLOWED, byte(f.Uint()))
+		case "DeliveryReport":
+			err = enc.writeByteParam(X_MMS_DELIVERY_REPORT, byte(f.Uint()))
+		case "ReadReport":
+			err = enc.writeByteParam(X_MMS_READ_REPORT, byte(f.Uint()))
+		case "Expiry":
+			expiry := f.Uint()
+			if expiry > 0 {
+				err = enc.writeRelativeExpiry(expiry)
+			}
+		default:
+			if encodeTag == "optional" {
+				log.Printf("Unhandled optional field %s", fieldName)
+			} else {
+				panic(fmt.Sprintf("missing encoding for mandatory field %s", fieldName))
+			}
+		}
+		if err != nil {
+			return fmt.Errorf("cannot encode field %s with value %s: %s ... encoded so far: %s", fieldName, f, err, enc.log)
+		}
+	}
+	return nil
+}
+
+func (enc *MMSEncoder) setParam(param byte) error {
+	return enc.writeByte(param | 0x80)
+}
+
+func encodeAttachment(attachment *Attachment) ([]byte, error) {
+	var outBytes bytes.Buffer
+	enc := NewEncoder(&outBytes)
+	if err := enc.Encode(attachment); err != nil {
+		return []byte{}, err
+	}
+	return outBytes.Bytes(), nil
+}
+
+func (enc *MMSEncoder) writeAttachments(attachments []*Attachment) error {
+	// Write the number of parts
+	if err := enc.writeUintVar(uint64(len(attachments))); err != nil {
+		return err
+	}
+
+	for i := range attachments {
+		var attachmentHeader []byte
+		if b, err := encodeAttachment(attachments[i]); err != nil {
+			return err
+		} else {
+			attachmentHeader = b
+		}
+
+		// headers length
+		headerLength := uint64(len(attachmentHeader))
+		if err := enc.writeUintVar(headerLength); err != nil {
+			return err
+		}
+		// data length
+		dataLength := uint64(len(attachments[i].Data))
+		if err := enc.writeUintVar(dataLength); err != nil {
+			return err
+		}
+		if err := enc.writeBytes(attachmentHeader, int(headerLength)); err != nil {
+			return err
+		}
+		if err := enc.writeBytes(attachments[i].Data, int(dataLength)); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (enc *MMSEncoder) writeCharset(charset string) error {
+	if charset == "" {
+		return nil
+	}
+	charsetCode := uint64(ANY_CHARSET)
+	for k, v := range CHARSETS {
+		if v == charset {
+			charsetCode = k
+		}
+	}
+	return enc.writeIntegerParam(WSP_PARAMETER_TYPE_CHARSET, charsetCode)
+}
+
+func (enc *MMSEncoder) writeLength(length uint64) error {
+	if length <= SHORT_LENGTH_MAX {
+		return enc.writeByte(byte(length))
+	} else {
+		if err := enc.writeByte(LENGTH_QUOTE); err != nil {
+			return err
+		}
+		return enc.writeUintVar(length)
+	}
+}
+
+func encodeContentType(media string) (uint64, error) {
+	var mt int
+	for mt = range CONTENT_TYPES {
+		if CONTENT_TYPES[mt] == media {
+			return uint64(mt), nil
+		}
+	}
+	return 0, errors.New("cannot binary encode media")
+}
+
+func (enc *MMSEncoder) writeContentType(media, start, ctype, name string) error {
+	if start == "" && ctype == "" && name == "" {
+		return enc.writeMediaType(media)
+	}
+
+	var contentType []byte
+	if start != "" {
+		contentType = append(contentType, WSP_PARAMETER_TYPE_START_DEFUNCT|SHORT_FILTER)
+		contentType = append(contentType, []byte(start)...)
+		contentType = append(contentType, 0)
+	}
+	if ctype != "" {
+		contentType = append(contentType, WSP_PARAMETER_TYPE_CONTENT_TYPE|SHORT_FILTER)
+		contentType = append(contentType, []byte(ctype)...)
+		contentType = append(contentType, 0)
+	}
+	if name != "" {
+		contentType = append(contentType, WSP_PARAMETER_TYPE_NAME_DEFUNCT|SHORT_FILTER)
+		contentType = append(contentType, []byte(name)...)
+		contentType = append(contentType, 0)
+	}
+
+	if mt, err := encodeContentType(media); err == nil {
+		// +1 for mt
+		length := uint64(len(contentType) + 1)
+		if err := enc.writeLength(length); err != nil {
+			return err
+		}
+		if err := enc.writeInteger(mt); err != nil {
+			return err
+		}
+	} else {
+		mediaB := []byte(media)
+		mediaB = append(mediaB, 0)
+		contentType = append(mediaB, contentType...)
+		length := uint64(len(contentType))
+		if err := enc.writeLength(length); err != nil {
+			return err
+		}
+	}
+	return enc.writeBytes(contentType, len(contentType))
+}
+
+func (enc *MMSEncoder) writeMediaType(media string) error {
+	if mt, err := encodeContentType(media); err == nil {
+		return enc.writeInteger(mt)
+	}
+
+	// +1 is the byte{0}
+	if err := enc.writeByte(byte(len(media) + 1)); err != nil {
+		return err
+	}
+	return enc.writeString(media)
+}
+
+func (enc *MMSEncoder) writeRelativeExpiry(expiry uint64) error {
+	if err := enc.setParam(X_MMS_EXPIRY); err != nil {
+		return err
+	}
+	encodedLong := encodeLong(expiry)
+
+	var b []byte
+	// +1 for the token, +1 for the len of long
+	b = append(b, byte(len(encodedLong)+2))
+	b = append(b, ExpiryTokenRelative)
+	b = append(b, byte(len(encodedLong)))
+	b = append(b, encodedLong...)
+
+	return enc.writeBytes(b, len(b))
+}
+
+func (enc *MMSEncoder) writeLongIntegerParam(param byte, i uint64) error {
+	if err := enc.setParam(param); err != nil {
+		return err
+	}
+	return enc.writeLongInteger(i)
+}
+
+func (enc *MMSEncoder) writeIntegerParam(param byte, i uint64) error {
+	if err := enc.setParam(param); err != nil {
+		return err
+	}
+	return enc.writeInteger(i)
+}
+
+func (enc *MMSEncoder) writeQuotedStringParam(param byte, s string) error {
+	if s == "" {
+		enc.log = enc.log + "Skipping empty string\n"
+	}
+	if err := enc.setParam(param); err != nil {
+		return err
+	}
+	if err := enc.writeByte(STRING_QUOTE); err != nil {
+		return err
+	}
+	return enc.writeString(s)
+}
+
+func (enc *MMSEncoder) writeStringParam(param byte, s string) error {
+	if s == "" {
+		enc.log = enc.log + "Skipping empty string\n"
+		return nil
+	}
+	if err := enc.setParam(param); err != nil {
+		return err
+	}
+	return enc.writeString(s)
+}
+
+func (enc *MMSEncoder) writeByteParam(param byte, b byte) error {
+	if err := enc.setParam(param); err != nil {
+		return err
+	}
+	return enc.writeByte(b)
+}
+
+func (enc *MMSEncoder) writeFrom() error {
+	if err := enc.setParam(FROM); err != nil {
+		return err
+	}
+	if err := enc.writeByte(1); err != nil {
+		return err
+	}
+	return enc.writeByte(TOKEN_INSERT_ADDRESS)
+}
+
+func (enc *MMSEncoder) writeString(s string) error {
+	bytes := []byte(s)
+	bytes = append(bytes, 0)
+	_, err := enc.w.Write(bytes)
+	return err
+}
+
+func (enc *MMSEncoder) writeBytes(b []byte, count int) error {
+	if n, err := enc.w.Write(b); n != count {
+		return fmt.Errorf("expected to write %d byte[s] but wrote %d", count, n)
+	} else if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (enc *MMSEncoder) writeByte(b byte) error {
+	return enc.writeBytes([]byte{b}, 1)
+}
+
+// writeShort encodes i according to the Basic Rules described in section
+// 8.4.2.2 of WAP-230-WSP-20010705-a.
+//
+// Integers in range 0-127 (< 0x80) shall be encoded as a one octet value
+// with the most significant bit set to one (1xxx xxxx == |0x80) and with
+// the value in the remaining least significant bits.
+func (enc *MMSEncoder) writeShortInteger(i uint64) error {
+	return enc.writeByte(byte(i | 0x80))
+}
+
+// writeLongInteger encodes i according to the Basic Rules described in section
+// 8.4.2.2 of WAP-230-WSP-20010705-a.
+//
+// Long-integer = Short-length Multi-octet-integer
+// The Short-length indicates the length of the Multi-octet-integer
+//
+// Multi-octet-integer = 1*30 OCTET
+// The content octets shall be an unsigned integer value
+// with the most significant octet encoded first (big-endian representation).
+// The minimum number of octets must be used to encode the value.
+func (enc *MMSEncoder) writeLongInteger(i uint64) error {
+	encodedLong := encodeLong(i)
+	encLength := uint64(len(encodedLong))
+	if encLength > SHORT_LENGTH_MAX {
+		return fmt.Errorf("cannot encode long integer, lenght was %d but expected %d", encLength, SHORT_LENGTH_MAX)
+	}
+	if err := enc.writeByte(byte(encLength)); err != nil {
+		return err
+	}
+
+	return enc.writeBytes(encodedLong, len(encodedLong))
+}
+
+func encodeLong(i uint64) (encodedLong []byte) {
+	for i > 0 {
+		b := byte(0xff & i)
+		encodedLong = append([]byte{b}, encodedLong...)
+		i = i >> 8
+	}
+	return encodedLong
+}
+
+// writeInteger encodes i according to the Basic Rules described in section
+// 8.4.2.2 of WAP-230-WSP-20010705-a.
+//
+// It encodes as a Short-integer when i < 128 (=0x80) or as a Long-Integer
+// otherwise
+func (enc *MMSEncoder) writeInteger(i uint64) error {
+	if i < 0x80 {
+		return enc.writeShortInteger(i)
+	} else {
+		return enc.writeLongInteger(i)
+	}
+	return nil
+}
+
+// writeUintVar encodes v according to section 8.1.2 and the Basic Rules
+// described in section 8.4.2.2 of WAP-230-WSP-20010705-a.
+//
+// To encode a large unsigned integer, split it into 7-bit (0x7f) fragments
+// and place them in the payloads of multiple octets. The most significant
+// bits are placed in the first octets with the least significant bits ending
+// up in the last octet. All octets MUST set the Continue bit to 1 (|0x80)
+// except the last octet, which MUST set the Continue bit to 0.
+//
+// The unsigned integer MUST be encoded in the smallest encoding possible.
+// In other words, the encoded value MUST NOT start with an octet with the
+// value 0x80.
+func (enc *MMSEncoder) writeUintVar(v uint64) error {
+	uintVar := []byte{byte(v & 0x7f)}
+	v = v >> 7
+	for v > 0 {
+		uintVar = append([]byte{byte(0x80 | (v & 0x7f))}, uintVar...)
+		v = v >> 7
+	}
+	return enc.writeBytes(uintVar, len(uintVar))
+}

=== added file 'mms/encoder_test.go'
--- mms/encoder_test.go	1970-01-01 00:00:00 +0000
+++ mms/encoder_test.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of mms.
+ *
+ * mms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * mms 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package mms
+
+import (
+	"bytes"
+	"io/ioutil"
+	"os"
+	"testing"
+
+	. "launchpad.net/gocheck"
+)
+
+type EncoderTestSuite struct{}
+
+// Hook up gocheck into the "go test" runner.
+func Test(t *testing.T) { TestingT(t) }
+
+var _ = Suite(&EncoderTestSuite{})
+
+func (s *EncoderTestSuite) TestEncodeMNotifyRespIndRetrievedWithReports(c *C) {
+	expectedBytes := []byte{
+		//Message Type m-notifyresp.ind
+		0x8C, 0x83,
+		// Transaction Id
+		0x98, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x00,
+		// MMS Version 1.3
+		0x8D, 0x93,
+		// Status retrieved
+		0x95, 0x81,
+		// Report Allowed No
+		0x91, 0x81,
+	}
+	mNotifyRespInd := &MNotifyRespInd{
+		UUID:          "1",
+		Type:          TYPE_NOTIFYRESP_IND,
+		TransactionId: "0123456",
+		Version:       MMS_MESSAGE_VERSION_1_3,
+		Status:        STATUS_RETRIEVED,
+		ReportAllowed: ReportAllowedNo,
+	}
+	var outBytes bytes.Buffer
+	enc := NewEncoder(&outBytes)
+	c.Assert(enc.Encode(mNotifyRespInd), IsNil)
+	c.Assert(outBytes.Bytes(), DeepEquals, expectedBytes)
+}
+
+func (s *EncoderTestSuite) TestEncodeMNotifyRespIndDeffered(c *C) {
+	expectedBytes := []byte{
+		//Message Type m-notifyresp.ind
+		0x8C, 0x83,
+		// Transaction Id
+		0x98, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x00,
+		// MMS Version 1.3
+		0x8D, 0x93,
+		// Status deffered
+		0x95, 0x83,
+		// Report Allowed No
+		0x91, 0x81,
+	}
+	mNotifyRespInd := &MNotifyRespInd{
+		UUID:          "1",
+		Type:          TYPE_NOTIFYRESP_IND,
+		TransactionId: "0123456",
+		Version:       MMS_MESSAGE_VERSION_1_3,
+		Status:        STATUS_DEFERRED,
+		ReportAllowed: ReportAllowedNo,
+	}
+	var outBytes bytes.Buffer
+	enc := NewEncoder(&outBytes)
+	c.Assert(enc.Encode(mNotifyRespInd), IsNil)
+	c.Assert(outBytes.Bytes(), DeepEquals, expectedBytes)
+}
+
+func (s *EncoderTestSuite) TestEncodeMNotifyRespIndRetrievedWithoutReports(c *C) {
+	expectedBytes := []byte{
+		//Message Type m-notifyresp.ind
+		0x8C, 0x83,
+		// Transaction Id
+		0x98, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x00,
+		// MMS Version 1.3
+		0x8D, 0x93,
+		// Status retrieved
+		0x95, 0x81,
+		// Report Allowed Yes
+		0x91, 0x80,
+	}
+	mNotifyRespInd := &MNotifyRespInd{
+		UUID:          "1",
+		Type:          TYPE_NOTIFYRESP_IND,
+		TransactionId: "0123456",
+		Version:       MMS_MESSAGE_VERSION_1_3,
+		Status:        STATUS_RETRIEVED,
+		ReportAllowed: ReportAllowedYes,
+	}
+	var outBytes bytes.Buffer
+	enc := NewEncoder(&outBytes)
+	c.Assert(enc.Encode(mNotifyRespInd), IsNil)
+	c.Assert(outBytes.Bytes(), DeepEquals, expectedBytes)
+}
+
+func (s *EncoderTestSuite) TestEncodeMSendReq(c *C) {
+	tmp, err := ioutil.TempFile("", "")
+	c.Assert(err, IsNil)
+	tmp.Close()
+	defer os.Remove(tmp.Name())
+	err = ioutil.WriteFile(tmp.Name(), []byte{1, 2, 3, 4, 5, 6}, 0644)
+	c.Assert(err, IsNil)
+
+	att, err := NewAttachment("text0", "text0.txt", tmp.Name())
+	c.Assert(err, IsNil)
+
+	attachments := []*Attachment{att}
+
+	recipients := []string{"+12345"}
+	mSendReq := NewMSendReq(recipients, attachments, false)
+
+	var outBytes bytes.Buffer
+	enc := NewEncoder(&outBytes)
+	err = enc.Encode(mSendReq)
+	c.Assert(err, IsNil)
+}

=== added file 'mms/mms.go'
--- mms/mms.go	1970-01-01 00:00:00 +0000
+++ mms/mms.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,438 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of mms.
+ *
+ * mms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * mms 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package mms
+
+import (
+	"errors"
+	"fmt"
+	"log"
+	"os"
+	"strings"
+	"time"
+)
+
+// MMS Field names from OMA-WAP-MMS section 7.3 Table 12
+const (
+	BCC                           = 0x01
+	CC                            = 0x02
+	X_MMS_CONTENT_LOCATION        = 0x03
+	CONTENT_TYPE                  = 0x04
+	DATE                          = 0x05
+	X_MMS_DELIVERY_REPORT         = 0x06
+	X_MMS_DELIVERY_TIME           = 0x07
+	X_MMS_EXPIRY                  = 0x08
+	FROM                          = 0x09
+	X_MMS_MESSAGE_CLASS           = 0x0A
+	MESSAGE_ID                    = 0x0B
+	X_MMS_MESSAGE_TYPE            = 0x0C
+	X_MMS_MMS_VERSION             = 0x0D
+	X_MMS_MESSAGE_SIZE            = 0x0E
+	X_MMS_PRIORITY                = 0x0F
+	X_MMS_READ_REPORT             = 0x10
+	X_MMS_REPORT_ALLOWED          = 0x11
+	X_MMS_RESPONSE_STATUS         = 0x12
+	X_MMS_RESPONSE_TEXT           = 0x13
+	X_MMS_SENDER_VISIBILITY       = 0x14
+	X_MMS_STATUS                  = 0x15
+	SUBJECT                       = 0x16
+	TO                            = 0x17
+	X_MMS_TRANSACTION_ID          = 0x18
+	X_MMS_RETRIEVE_STATUS         = 0x19
+	X_MMS_RETRIEVE_TEXT           = 0x1A
+	X_MMS_READ_STATUS             = 0x1B
+	X_MMS_REPLY_CHARGING          = 0x1C
+	X_MMS_REPLY_CHARGING_DEADLINE = 0x1D
+	X_MMS_REPLY_CHARGING_ID       = 0x1E
+	X_MMS_REPLY_CHARGING_SIZE     = 0x1F
+	X_MMS_PREVIOUSLY_SENT_BY      = 0x20
+	X_MMS_PREVIOUSLY_SENT_DATE    = 0x21
+)
+
+// MMS Content Type Assignments OMA-WAP-MMS section 7.3 Table 13
+const (
+	PUSH_APPLICATION_ID = 4
+	VND_WAP_MMS_MESSAGE = "application/vnd.wap.mms-message"
+)
+
+const (
+	TYPE_SEND_REQ         = 0x80
+	TYPE_SEND_CONF        = 0x81
+	TYPE_NOTIFICATION_IND = 0x82
+	TYPE_NOTIFYRESP_IND   = 0x83
+	TYPE_RETRIEVE_CONF    = 0x84
+	TYPE_ACKNOWLEDGE_IND  = 0x85
+	TYPE_DELIVERY_IND     = 0x86
+)
+
+const (
+	MMS_MESSAGE_VERSION_1_0 = 0x90
+	MMS_MESSAGE_VERSION_1_1 = 0x91
+	MMS_MESSAGE_VERSION_1_2 = 0x92
+	MMS_MESSAGE_VERSION_1_3 = 0x93
+)
+
+// Delivery Report defined in OMA-WAP-MMS section 7.2.6
+const (
+	DeliveryReportYes byte = 128
+	DeliveryReportNo  byte = 129
+)
+
+// Expiry tokens defined in OMA-WAP-MMS section 7.2.10
+const (
+	ExpiryTokenAbsolute byte = 128
+	ExpiryTokenRelative byte = 129
+)
+
+// From tokens defined in OMA-WAP-MMS section 7.2.11
+const (
+	TOKEN_ADDRESS_PRESENT = 0x80
+	TOKEN_INSERT_ADDRESS  = 0x81
+)
+
+// Message classes defined in OMA-WAP-MMS section 7.2.14
+const (
+	ClassPersonal      byte = 128
+	ClassAdvertisement byte = 129
+	ClassInformational byte = 130
+	ClassAuto          byte = 131
+)
+
+// Report Report defined in OMA-WAP-MMS 7.2.20
+const (
+	ReadReportYes byte = 128
+	ReadReportNo  byte = 129
+)
+
+// Report Allowed defined in OMA-WAP-MMS section 7.2.26
+const (
+	ReportAllowedYes byte = 128
+	ReportAllowedNo  byte = 129
+)
+
+// Response Status defined in OMA-WAP-MMS section 7.2.27
+//
+// An MMS Client MUST react the same to a value in range 196 to 223 as it
+// does to the value 192 (Error-transient-failure).
+//
+// An MMS Client MUST react the same to a value in range 234 to 255 as it
+// does to the value 224 (Error-permanent-failure).
+//
+// Any other values SHALL NOT be used. They are reserved for future use.
+// An MMS Client that receives such a reserved value MUST react the same
+// as it does to the value 224 (Error-permanent-failure).
+const (
+	ResponseStatusOk                            byte = 128
+	ResponseStatusErrorUnspecified              byte = 129 // Obsolete
+	ResponseStatusErrorServiceDenied            byte = 130 // Obsolete
+	ResponseStatusErrorMessageFormatCorrupt     byte = 131 // Obsolete
+	ResponseStatusErrorSendingAddressUnresolved byte = 132 // Obsolete
+	ResponseStatusErrorMessageNotFound          byte = 133 // Obsolete
+	ResponseStatusErrorNetworkProblem           byte = 134 // Obsolete
+	ResponseStatusErrorContentNotAccepted       byte = 135 // Obsolete
+	ResponseStatusErrorUnsupportedMessage       byte = 136
+
+	ResponseStatusErrorTransientFailure           byte = 192
+	ResponseStatusErrorTransientAddressUnresolved byte = 193
+	ResponseStatusErrorTransientMessageNotFound   byte = 194
+	ResponseStatusErrorTransientNetworkProblem    byte = 195
+
+	ResponseStatusErrorTransientMaxReserved byte = 223
+
+	ResponseStatusErrorPermanentFailure                         byte = 224
+	ResponseStatusErrorPermanentServiceDenied                   byte = 225
+	ResponseStatusErrorPermanentMessageFormatCorrupt            byte = 226
+	ResponseStatusErrorPermanentAddressUnresolved               byte = 227
+	ResponseStatusErrorPermanentMessageNotFound                 byte = 228
+	ResponseStatusErrorPermanentContentNotAccepted              byte = 229
+	ResponseStatusErrorPermanentReplyChargingLimitationsNotMet  byte = 230
+	ResponseStatusErrorPermanentReplyChargingRequestNotAccepted byte = 231
+	ResponseStatusErrorPermanentReplyChargingForwardingDenied   byte = 232
+	ResponseStatusErrorPermanentReplyChargingNotSupported       byte = 233
+
+	ResponseStatusErrorPermamentMaxReserved byte = 255
+)
+
+// Status defined in OMA-WAP-MMS section 7.2.23
+const (
+	STATUS_EXPIRED      = 128
+	STATUS_RETRIEVED    = 129
+	STATUS_REJECTED     = 130
+	STATUS_DEFERRED     = 131
+	STATUS_UNRECOGNIZED = 132
+)
+
+// MSendReq holds a m-send.req message defined in
+// OMA-WAP-MMS-ENC-v1.1 section 6.1.1
+type MSendReq struct {
+	UUID             string `encode:"no"`
+	Type             byte
+	TransactionId    string
+	Version          byte
+	Date             uint64 `encode:"optional"`
+	From             string
+	To               string
+	Cc               string `encode:"no"`
+	Bcc              string `encode:"no"`
+	Subject          string `encode:"optional"`
+	Class            byte   `encode:"optional"`
+	Expiry           uint64 `encode:"optional"`
+	DeliveryTime     uint64 `encode:"optional"`
+	Priority         byte   `encode:"optional"`
+	SenderVisibility byte   `encode:"optional"`
+	DeliveryReport   byte   `encode:"optional"`
+	ReadReport       byte   `encode:"optional"`
+	ContentTypeStart string `encode:"no"`
+	ContentTypeType  string `encode:"no"`
+	ContentType      string
+	Attachments      []*Attachment `encode:"no"`
+}
+
+// MSendReq holds a m-send.conf message defined in
+// OMA-WAP-MMS-ENC section 6.1.2
+type MSendConf struct {
+	Type           byte
+	TransactionId  string
+	Version        byte
+	ResponseStatus byte
+	ResponseText   string
+	MessageId      string
+}
+
+// MNotificationInd holds a m-notification.ind message defined in
+// OMA-WAP-MMS-ENC section 6.2
+type MNotificationInd struct {
+	MMSReader
+	UUID                                 string
+	Type, Version, Class, DeliveryReport byte
+	ReplyCharging, ReplyChargingDeadline byte
+	ReplyChargingId                      string
+	TransactionId, ContentLocation       string
+	From, Subject                        string
+	Expiry, Size                         uint64
+}
+
+// MNotificationInd holds a m-notifyresp.ind message defined in
+// OMA-WAP-MMS-ENC-v1.1 section 6.2
+type MNotifyRespInd struct {
+	UUID          string `encode:"no"`
+	Type          byte
+	TransactionId string
+	Version       byte
+	Status        byte
+	ReportAllowed byte `encode:"optional"`
+}
+
+// MRetrieveConf holds a m-retrieve.conf message defined in
+// OMA-WAP-MMS-ENC-v1.1 section 6.3
+type MRetrieveConf struct {
+	MMSReader
+	UUID                                       string
+	Type, Version, Status, Class, Priority     byte
+	ReplyCharging, ReplyChargingDeadline       byte
+	ReplyChargingId                            string
+	ReadReport, RetrieveStatus, DeliveryReport byte
+	TransactionId, MessageId, RetrieveText     string
+	From, Cc, Subject                          string
+	To                                         string
+	ReportAllowed                              byte
+	Date                                       uint64
+	Content                                    Attachment
+	Attachments                                []Attachment
+	Data                                       []byte
+}
+
+type MMSReader interface{}
+type MMSWriter interface{}
+
+// NewMSendReq creates a personal message with a normal priority and no read report
+func NewMSendReq(recipients []string, attachments []*Attachment, deliveryReport bool) *MSendReq {
+	for i := range recipients {
+		recipients[i] += "/TYPE=PLMN"
+	}
+	uuid := genUUID()
+
+	orderedAttachments, smilStart, smilType := processAttachments(attachments)
+
+	return &MSendReq{
+		Type:          TYPE_SEND_REQ,
+		To:            strings.Join(recipients, ","),
+		TransactionId: uuid,
+		Version:       MMS_MESSAGE_VERSION_1_1,
+		UUID:          uuid,
+		Date:          getDate(),
+		// this will expire the message in 7 days
+		Expiry:           uint64(time.Duration(time.Hour * 24 * 7).Seconds()),
+		DeliveryReport:   getDeliveryReport(deliveryReport),
+		ReadReport:       ReadReportNo,
+		Class:            ClassPersonal,
+		ContentType:      "application/vnd.wap.multipart.related",
+		ContentTypeStart: smilStart,
+		ContentTypeType:  smilType,
+		Attachments:      orderedAttachments,
+	}
+}
+
+func NewMSendConf() *MSendConf {
+	return &MSendConf{
+		Type: TYPE_SEND_CONF,
+	}
+}
+
+func NewMNotificationInd() *MNotificationInd {
+	return &MNotificationInd{Type: TYPE_NOTIFICATION_IND, UUID: genUUID()}
+}
+
+func (mNotificationInd *MNotificationInd) NewMNotifyRespInd(status byte, deliveryReport bool) *MNotifyRespInd {
+	return &MNotifyRespInd{
+		Type:          TYPE_NOTIFYRESP_IND,
+		UUID:          mNotificationInd.UUID,
+		TransactionId: mNotificationInd.TransactionId,
+		Version:       mNotificationInd.Version,
+		Status:        status,
+		ReportAllowed: getReportAllowed(deliveryReport),
+	}
+}
+
+func (mRetrieveConf *MRetrieveConf) NewMNotifyRespInd(deliveryReport bool) *MNotifyRespInd {
+	return &MNotifyRespInd{
+		Type:          TYPE_NOTIFYRESP_IND,
+		UUID:          mRetrieveConf.UUID,
+		TransactionId: mRetrieveConf.TransactionId,
+		Version:       mRetrieveConf.Version,
+		Status:        STATUS_RETRIEVED,
+		ReportAllowed: getReportAllowed(deliveryReport),
+	}
+}
+
+func NewMNotifyRespInd() *MNotifyRespInd {
+	return &MNotifyRespInd{Type: TYPE_NOTIFYRESP_IND}
+}
+
+func NewMRetrieveConf(uuid string) *MRetrieveConf {
+	return &MRetrieveConf{Type: TYPE_RETRIEVE_CONF, UUID: uuid}
+}
+
+func genUUID() string {
+	var id string
+	random, err := os.Open("/dev/urandom")
+	if err != nil {
+		id = "1234567890ABCDEF"
+	} else {
+		defer random.Close()
+		b := make([]byte, 16)
+		random.Read(b)
+		id = fmt.Sprintf("%x", b)
+	}
+	return id
+}
+
+var ErrTransient = errors.New("Error-transient-failure")
+var ErrPermanent = errors.New("Error-permament-failure")
+
+func (mSendConf *MSendConf) Status() error {
+	s := mSendConf.ResponseStatus
+	// these are case by case Response Status and we need to determine each one
+	switch s {
+	case ResponseStatusOk:
+		return nil
+	case ResponseStatusErrorUnspecified:
+		return ErrTransient
+	case ResponseStatusErrorServiceDenied:
+		return ErrTransient
+	case ResponseStatusErrorMessageFormatCorrupt:
+		return ErrPermanent
+	case ResponseStatusErrorSendingAddressUnresolved:
+		return ErrPermanent
+	case ResponseStatusErrorMessageNotFound:
+		// this could be ErrTransient or ErrPermanent
+		return ErrPermanent
+	case ResponseStatusErrorNetworkProblem:
+		return ErrTransient
+	case ResponseStatusErrorContentNotAccepted:
+		return ErrPermanent
+	case ResponseStatusErrorUnsupportedMessage:
+		return ErrPermanent
+	}
+
+	// these are the Response Status we can group
+	if s >= ResponseStatusErrorTransientFailure && s <= ResponseStatusErrorTransientMaxReserved {
+		return ErrTransient
+	} else if s >= ResponseStatusErrorPermanentFailure && s <= ResponseStatusErrorPermamentMaxReserved {
+		return ErrPermanent
+	}
+
+	// any case not handled is a permanent error
+	return ErrPermanent
+}
+
+func getReadReport(v bool) (read byte) {
+	if v {
+		read = ReadReportYes
+	} else {
+		read = ReadReportNo
+	}
+	return read
+}
+
+func getDeliveryReport(v bool) (delivery byte) {
+	if v {
+		delivery = DeliveryReportYes
+	} else {
+		delivery = DeliveryReportNo
+	}
+	return delivery
+}
+
+func getReportAllowed(v bool) (allowed byte) {
+	if v {
+		allowed = ReportAllowedYes
+	} else {
+		allowed = ReportAllowedNo
+	}
+	return allowed
+}
+
+func getDate() (date uint64) {
+	d := time.Now().Unix()
+	if d > 0 {
+		date = uint64(d)
+	}
+	return date
+}
+
+func processAttachments(a []*Attachment) (oa []*Attachment, smilStart, smilType string) {
+	oa = make([]*Attachment, 0, len(a))
+	for i := range a {
+		if strings.HasPrefix(a[i].MediaType, "application/smil") {
+			oa = append([]*Attachment{a[i]}, oa...)
+			var err error
+			smilStart, err = getSmilStart(a[i].Data)
+			if err != nil {
+				log.Println("Cannot set content type start:", err)
+			}
+			smilType = "application/smil"
+		} else {
+			oa = append(oa, a[i])
+		}
+	}
+	return oa, smilStart, smilType
+}

=== added file 'mms/mms_test.go'
--- mms/mms_test.go	1970-01-01 00:00:00 +0000
+++ mms/mms_test.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of mms.
+ *
+ * mms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * mms 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package mms
+
+import . "launchpad.net/gocheck"
+
+type MMSTestSuite struct{}
+
+var _ = Suite(&MMSTestSuite{})
+
+func (s *MMSTestSuite) TestNewMSendReq(c *C) {
+	recipients := []string{"+11111", "+22222", "+33333"}
+	recipientsStr := "+11111/TYPE=PLMN,+22222/TYPE=PLMN,+33333/TYPE=PLMN"
+	mSendReq := NewMSendReq(recipients, []*Attachment{}, false)
+	c.Check(mSendReq.To, Equals, recipientsStr)
+	c.Check(mSendReq.ContentType, Equals, "application/vnd.wap.multipart.related")
+	c.Check(mSendReq.Type, Equals, byte(TYPE_SEND_REQ))
+}

=== added file 'mms/parameters.go'
--- mms/parameters.go	1970-01-01 00:00:00 +0000
+++ mms/parameters.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of mms.
+ *
+ * mms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * mms 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package mms
+
+//Table 38 of Well-Known Parameter Assignments from OMA-WAP-MMS section 7.3
+const (
+	WSP_PARAMETER_TYPE_Q                  = 0x00 // Version 1.1 Q-value
+	WSP_PARAMETER_TYPE_CHARSET            = 0x01 // Version 1.1 Well-known-charset
+	WSP_PARAMETER_TYPE_LEVEL              = 0x02 // Version 1.1 Version-value
+	WSP_PARAMETER_TYPE_TYPE               = 0x03 // Version 1.1 Integer-value
+	WSP_PARAMETER_TYPE_NAME_DEFUNCT       = 0x05 // Version 1.1 Text-string
+	WSP_PARAMETER_TYPE_FILENAME_DEFUNCT   = 0x06 // Version 1.1 Text-string
+	WSP_PARAMETER_TYPE_DIFFERENCES        = 0x07 // Version 1.1 Field-name
+	WSP_PARAMETER_TYPE_PADDING            = 0x08 // Version 1.1 Short-integer
+	WSP_PARAMETER_TYPE_CONTENT_TYPE       = 0x09 // Version 1.2 Constrained-encoding
+	WSP_PARAMETER_TYPE_START_DEFUNCT      = 0x0A // Version 1.2 Text-string
+	WSP_PARAMETER_TYPE_START_INFO_DEFUNCT = 0x0B // Version 1.2 Text-string
+	WSP_PARAMETER_TYPE_COMMENT_DEFUNCT    = 0x0C // Version 1.3 Text-string
+	WSP_PARAMETER_TYPE_DOMAIN_DEFUNCT     = 0x0D // Version 1.3 Text-string
+	WSP_PARAMETER_TYPE_MAX_AGE            = 0x0E // Version 1.3 Delta-seconds-value
+	WSP_PARAMETER_TYPE_PATH_DEFUNCT       = 0x0F // Version 1.3 Text-string
+	WSP_PARAMETER_TYPE_SECURE             = 0x10 // Version 1.3 No-value
+	WSP_PARAMETER_TYPE_SEC                = 0x11 // Version 1.4 Short-integer
+	WSP_PARAMETER_TYPE_MAC                = 0x12 // Version 1.4 Text-value
+	WSP_PARAMETER_TYPE_CREATION_DATE      = 0x13 // Version 1.4 Date-value
+	WSP_PARAMETER_TYPE_MODIFICATION_DATE  = 0x14 // Version 1.4 Date-value
+	WSP_PARAMETER_TYPE_READ_DATE          = 0x15 // Version 1.4 Date-value
+	WSP_PARAMETER_TYPE_SIZE               = 0x16 // Version 1.4 Integer-value
+	WSP_PARAMETER_TYPE_NAME               = 0x17 // Version 1.4 Text-value
+	WSP_PARAMETER_TYPE_FILENAME           = 0x18 // Version 1.4 Text-value
+	WSP_PARAMETER_TYPE_START              = 0x19 // Version 1.4 Text-value
+	WSP_PARAMETER_TYPE_START_INFO         = 0x1A // Version 1.4 Text-value
+	WSP_PARAMETER_TYPE_COMMENT            = 0x1B // Version 1.4 Text-value
+	WSP_PARAMETER_TYPE_DOMAIN             = 0x1C // Version 1.4 Text-value
+	WSP_PARAMETER_TYPE_PATH               = 0x1D // Version 1.4 Text-value
+	WSP_PARAMETER_TYPE_UNTYPED            = 0xFF // Version 1.4 Text-value
+)
+
+const (
+	MMS_PART_CONTENT_LOCATION = 0x0E
+	MMS_PART_CONTENT_ID       = 0x40
+)
+
+const (
+	TEXT_MAX         = 127
+	TEXT_MIN         = 32
+	SHORT_LENGTH_MAX = 30
+	LENGTH_QUOTE     = 31
+	STRING_QUOTE     = 34
+	SHORT_FILTER     = 0x80
+)
+
+const (
+	ANY_CHARSET = 128
+)
+
+var CONTENT_TYPES []string = []string{
+	"*/*", "text/*", "text/html", "text/plain",
+	"text/x-hdml", "text/x-ttml", "text/x-vCalendar",
+	"text/x-vCard", "text/vnd.wap.wml",
+	"text/vnd.wap.wmlscript", "text/vnd.wap.wta-event",
+	"multipart/*", "multipart/mixed", "multipart/form-data",
+	"multipart/byterantes", "multipart/alternative",
+	"application/*", "application/java-vm",
+	"application/x-www-form-urlencoded",
+	"application/x-hdmlc", "application/vnd.wap.wmlc",
+	"application/vnd.wap.wmlscriptc",
+	"application/vnd.wap.wta-eventc",
+	"application/vnd.wap.uaprof",
+	"application/vnd.wap.wtls-ca-certificate",
+	"application/vnd.wap.wtls-user-certificate",
+	"application/x-x509-ca-cert",
+	"application/x-x509-user-cert",
+	"image/*", "image/gif", "image/jpeg", "image/tiff",
+	"image/png", "image/vnd.wap.wbmp",
+	"application/vnd.wap.multipart.*",
+	"application/vnd.wap.multipart.mixed",
+	"application/vnd.wap.multipart.form-data",
+	"application/vnd.wap.multipart.byteranges",
+	"application/vnd.wap.multipart.alternative",
+	"application/xml", "text/xml",
+	"application/vnd.wap.wbxml",
+	"application/x-x968-cross-cert",
+	"application/x-x968-ca-cert",
+	"application/x-x968-user-cert",
+	"text/vnd.wap.si",
+	"application/vnd.wap.sic",
+	"text/vnd.wap.sl",
+	"application/vnd.wap.slc",
+	"text/vnd.wap.co",
+	"application/vnd.wap.coc",
+	"application/vnd.wap.multipart.related",
+	"application/vnd.wap.sia",
+	"text/vnd.wap.connectivity-xml",
+	"application/vnd.wap.connectivity-wbxml",
+	"application/pkcs7-mime",
+	"application/vnd.wap.hashed-certificate",
+	"application/vnd.wap.signed-certificate",
+	"application/vnd.wap.cert-response",
+	"application/xhtml+xml",
+	"application/wml+xml",
+	"text/css",
+	"application/vnd.wap.mms-message",
+	"application/vnd.wap.rollover-certificate",
+	"application/vnd.wap.locc+wbxml",
+	"application/vnd.wap.loc+xml",
+	"application/vnd.syncml.dm+wbxml",
+	"application/vnd.syncml.dm+xml",
+	"application/vnd.syncml.notification",
+	"application/vnd.wap.xhtml+xml",
+	"application/vnd.wv.csp.cir",
+	"application/vnd.oma.dd+xml",
+	"application/vnd.oma.drm.message",
+	"application/vnd.oma.drm.content",
+	"application/vnd.oma.drm.rights+xml",
+	"application/vnd.oma.drm.rights+wbxml",
+}
+
+var CHARSETS map[uint64]string = map[uint64]string{
+	0x07EA: "big5",
+	0x03E8: "iso-10646-ucs-2",
+	0x04:   "iso-8859-1",
+	0x05:   "iso-8859-2",
+	0x06:   "iso-8859-3",
+	0x07:   "iso-8859-4",
+	0x08:   "iso-8859-5",
+	0x09:   "iso-8859-6",
+	0x0A:   "iso-8859-7",
+	0x0B:   "iso-8859-8",
+	0x0C:   "iso-8859-9",
+	0x11:   "shift_JIS",
+	0x03:   "us-ascii",
+	0x6A:   "utf-8",
+}

=== added directory 'mms/test_payloads'
=== added file 'mms/test_payloads/m-send.conf_success'
Binary files mms/test_payloads/m-send.conf_success	1970-01-01 00:00:00 +0000 and mms/test_payloads/m-send.conf_success	2015-09-02 10:42:28 +0000 differ
=== added directory 'ofono'
=== renamed directory 'ofono' => 'ofono.moved'
=== added file 'ofono/common.go'
--- ofono/common.go	1970-01-01 00:00:00 +0000
+++ ofono/common.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of nuntium.
+ *
+ * nuntium is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * nuntium 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package ofono
+
+import "launchpad.net/go-dbus/v1"
+
+const (
+	AGENT_TAG                         = dbus.ObjectPath("/nuntium")
+	PUSH_NOTIFICATION_INTERFACE       = "org.ofono.PushNotification"
+	PUSH_NOTIFICATION_AGENT_INTERFACE = "org.ofono.PushNotificationAgent"
+	CONNECTION_MANAGER_INTERFACE      = "org.ofono.ConnectionManager"
+	CONNECTION_CONTEXT_INTERFACE      = "org.ofono.ConnectionContext"
+	SIM_MANAGER_INTERFACE             = "org.ofono.SimManager"
+	OFONO_MANAGER_INTERFACE           = "org.ofono.Manager"
+	OFONO_SENDER                      = "org.ofono"
+	MODEM_INTERFACE                   = "org.ofono.Modem"
+)
+
+type PropertiesType map[string]dbus.Variant
+
+func getModems(conn *dbus.Connection) (modemPaths []dbus.ObjectPath, err error) {
+	modemsReply, err := getOfonoProps(conn, "/", OFONO_SENDER, "org.ofono.Manager", "GetModems")
+	if err != nil {
+		return nil, err
+	}
+	for _, modemReply := range modemsReply {
+		modemPaths = append(modemPaths, modemReply.ObjectPath)
+	}
+	return modemPaths, nil
+}
+
+func connectToPropertySignal(conn *dbus.Connection, path dbus.ObjectPath, inter string) (*dbus.SignalWatch, error) {
+	w, err := conn.WatchSignal(&dbus.MatchRule{
+		Type:      dbus.TypeSignal,
+		Sender:    OFONO_SENDER,
+		Interface: inter,
+		Member:    "PropertyChanged",
+		Path:      path})
+	return w, err
+}
+
+func connectToSignal(conn *dbus.Connection, path dbus.ObjectPath, inter, member string) (*dbus.SignalWatch, error) {
+	w, err := conn.WatchSignal(&dbus.MatchRule{
+		Type:      dbus.TypeSignal,
+		Sender:    OFONO_SENDER,
+		Interface: inter,
+		Member:    member,
+		Path:      path})
+	return w, err
+}

=== added file 'ofono/context_test.go'
--- ofono/context_test.go	1970-01-01 00:00:00 +0000
+++ ofono/context_test.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,342 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@canonical.com
+ *
+ * This file is part of mms.
+ *
+ * mms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * mms 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package ofono
+
+import (
+	"errors"
+	"fmt"
+
+	"launchpad.net/go-dbus/v1"
+	. "launchpad.net/gocheck"
+)
+
+type ContextTestSuite struct {
+	modem    Modem
+	contexts []OfonoContext
+}
+
+var _ = Suite(&ContextTestSuite{})
+
+var proxy ProxyInfo
+
+func makeGenericContextProperty(name, cType string, active, messageCenter, messageProxy bool) PropertiesType {
+	p := make(PropertiesType)
+	p["Name"] = dbus.Variant{name}
+	p["Type"] = dbus.Variant{cType}
+	p["Active"] = dbus.Variant{active}
+	if messageCenter {
+		p["MessageCenter"] = dbus.Variant{"http://messagecenter.com"}
+	} else {
+		p["MessageCenter"] = dbus.Variant{""}
+	}
+	if messageProxy {
+		p["MessageProxy"] = dbus.Variant{proxy.String()}
+	} else {
+		p["MessageProxy"] = dbus.Variant{""}
+	}
+	return p
+}
+
+func (s *ContextTestSuite) SetUpSuite(c *C) {
+}
+
+func (s *ContextTestSuite) SetUpTest(c *C) {
+	s.modem = Modem{}
+	s.contexts = []OfonoContext{}
+	proxy = ProxyInfo{
+		Host: "4.4.4.4",
+		Port: 9999,
+	}
+	getOfonoProps = func(conn *dbus.Connection, objectPath dbus.ObjectPath, destination, iface, method string) (oProps []OfonoContext, err error) {
+		return s.contexts, nil
+	}
+}
+
+func (s *ContextTestSuite) TestNoContext(c *C) {
+	context, err := s.modem.GetMMSContexts("")
+	c.Check(context, IsNil)
+	c.Assert(err, DeepEquals, errors.New("No mms contexts found"))
+}
+
+func (s *ContextTestSuite) TestMMSOverInternet(c *C) {
+	context1 := OfonoContext{
+		ObjectPath: "/ril_0/context1",
+		Properties: makeGenericContextProperty("Context1", contextTypeInternet, true, true, true),
+	}
+	s.contexts = append(s.contexts, context1)
+
+	contexts, err := s.modem.GetMMSContexts("")
+	c.Assert(err, IsNil)
+	c.Assert(len(contexts), Equals, 1)
+	c.Check(contexts[0], DeepEquals, context1)
+}
+
+func (s *ContextTestSuite) TestMMSOverInactiveInternet(c *C) {
+	context1 := OfonoContext{
+		ObjectPath: "/ril_0/context1",
+		Properties: makeGenericContextProperty("Context1", contextTypeInternet, false, true, true),
+	}
+	s.contexts = append(s.contexts, context1)
+
+	context, err := s.modem.GetMMSContexts("")
+	c.Check(context, IsNil)
+	c.Assert(err, DeepEquals, errors.New("No mms contexts found"))
+}
+
+func (s *ContextTestSuite) TestMMSOverInternetNoProxy(c *C) {
+	context1 := OfonoContext{
+		ObjectPath: "/ril_0/context1",
+		Properties: makeGenericContextProperty("Context1", contextTypeInternet, true, true, false),
+	}
+	s.contexts = append(s.contexts, context1)
+
+	contexts, err := s.modem.GetMMSContexts("")
+	c.Assert(err, IsNil)
+	c.Assert(len(contexts), Equals, 1)
+	c.Check(contexts[0], DeepEquals, context1)
+}
+
+func (s *ContextTestSuite) TestMMSOverMMS(c *C) {
+	context1 := OfonoContext{
+		ObjectPath: "/ril_0/context1",
+		Properties: makeGenericContextProperty("Context1", contextTypeInternet, true, false, false),
+	}
+	s.contexts = append(s.contexts, context1)
+
+	context2 := OfonoContext{
+		ObjectPath: "/ril_0/context2",
+		Properties: makeGenericContextProperty("Context2", contextTypeMMS, false, true, true),
+	}
+	s.contexts = append(s.contexts, context2)
+
+	contexts, err := s.modem.GetMMSContexts("")
+	c.Assert(err, IsNil)
+	c.Assert(len(contexts), Equals, 1)
+	c.Check(contexts[0], DeepEquals, context2)
+}
+
+func (s *ContextTestSuite) TestMMSOverMMSNoProxy(c *C) {
+	context1 := OfonoContext{
+		ObjectPath: "/ril_0/context1",
+		Properties: makeGenericContextProperty("Context1", contextTypeInternet, true, false, false),
+	}
+	s.contexts = append(s.contexts, context1)
+
+	context2 := OfonoContext{
+		ObjectPath: "/ril_0/context2",
+		Properties: makeGenericContextProperty("Context2", contextTypeMMS, false, true, false),
+	}
+	s.contexts = append(s.contexts, context2)
+
+	contexts, err := s.modem.GetMMSContexts("")
+	c.Assert(err, IsNil)
+	c.Assert(len(contexts), Equals, 1)
+	c.Check(contexts[0], DeepEquals, context2)
+}
+
+func (s *ContextTestSuite) TestMMSMoreThanOneValid(c *C) {
+	context1 := OfonoContext{
+		ObjectPath: "/ril_0/context1",
+		Properties: makeGenericContextProperty("Context1", contextTypeInternet, true, true, false),
+	}
+	s.contexts = append(s.contexts, context1)
+
+	context2 := OfonoContext{
+		ObjectPath: "/ril_0/context2",
+		Properties: makeGenericContextProperty("Context2", contextTypeMMS, false, true, false),
+	}
+	s.contexts = append(s.contexts, context2)
+
+	contexts, err := s.modem.GetMMSContexts("")
+	c.Assert(err, IsNil)
+	c.Assert(len(contexts), Equals, 2)
+	c.Check(contexts[0], DeepEquals, context1)
+	c.Check(contexts[1], DeepEquals, context2)
+}
+
+func (s *ContextTestSuite) TestMMSMoreThanOneValidContextSelectPreferred(c *C) {
+	context1 := OfonoContext{
+		ObjectPath: "/ril_0/context1",
+		Properties: makeGenericContextProperty("Context1", contextTypeInternet, true, true, false),
+	}
+	s.contexts = append(s.contexts, context1)
+
+	context2 := OfonoContext{
+		ObjectPath: "/ril_0/context2",
+		Properties: makeGenericContextProperty("Context2", contextTypeMMS, false, true, false),
+	}
+	s.contexts = append(s.contexts, context2)
+
+	context3 := OfonoContext{
+		ObjectPath: "/ril_0/context3",
+		Properties: makeGenericContextProperty("Context3", contextTypeMMS, false, true, false),
+	}
+	s.contexts = append(s.contexts, context3)
+
+	contexts, err := s.modem.GetMMSContexts("/ril_0/context2")
+	c.Assert(err, IsNil)
+	c.Assert(len(contexts), Equals, 3)
+	c.Check(contexts[0], DeepEquals, context2)
+	c.Check(contexts[1], DeepEquals, context1)
+	c.Check(contexts[2], DeepEquals, context3)
+}
+
+func (s *ContextTestSuite) TestMMSMoreThanOneValidContextPreferredNoMatch(c *C) {
+	context1 := OfonoContext{
+		ObjectPath: "/ril_0/context1",
+		Properties: makeGenericContextProperty("Context1", contextTypeInternet, true, true, false),
+	}
+	s.contexts = append(s.contexts, context1)
+
+	context2 := OfonoContext{
+		ObjectPath: "/ril_0/context2",
+		Properties: makeGenericContextProperty("Context2", contextTypeMMS, false, true, false),
+	}
+	s.contexts = append(s.contexts, context2)
+
+	context3 := OfonoContext{
+		ObjectPath: "/ril_0/context3",
+		Properties: makeGenericContextProperty("Context3", contextTypeMMS, false, true, false),
+	}
+	s.contexts = append(s.contexts, context3)
+
+	contexts, err := s.modem.GetMMSContexts("/ril_0/context25")
+	c.Assert(err, IsNil)
+	c.Assert(len(contexts), Equals, 3)
+	c.Check(contexts[0], DeepEquals, context1)
+	c.Check(contexts[1], DeepEquals, context2)
+	c.Check(contexts[2], DeepEquals, context3)
+}
+
+func (s *ContextTestSuite) TestMMSMoreThanOneValidContext2Active(c *C) {
+	context0 := OfonoContext{
+		ObjectPath: "/ril_0/context0",
+		Properties: makeGenericContextProperty("Context0", contextTypeInternet, false, true, false),
+	}
+	s.contexts = append(s.contexts, context0)
+
+	context1 := OfonoContext{
+		ObjectPath: "/ril_0/context1",
+		Properties: makeGenericContextProperty("Context1", contextTypeMMS, false, true, false),
+	}
+	s.contexts = append(s.contexts, context1)
+
+	context2 := OfonoContext{
+		ObjectPath: "/ril_0/context2",
+		Properties: makeGenericContextProperty("Context2", contextTypeMMS, false, true, false),
+	}
+	s.contexts = append(s.contexts, context2)
+
+	context3 := OfonoContext{
+		ObjectPath: "/ril_0/context3",
+		Properties: makeGenericContextProperty("Context3", contextTypeMMS, true, true, false),
+	}
+	s.contexts = append(s.contexts, context3)
+
+	contexts, err := s.modem.GetMMSContexts("")
+	c.Assert(err, IsNil)
+	c.Assert(len(contexts), Equals, 3)
+	c.Check(contexts[0], DeepEquals, context3)
+	c.Check(contexts[1], DeepEquals, context1)
+	c.Check(contexts[2], DeepEquals, context2)
+}
+
+func (s *ContextTestSuite) TestMMSMoreThanOneValidContextPreferredNotActive(c *C) {
+	context0 := OfonoContext{
+		ObjectPath: "/ril_0/context0",
+		Properties: makeGenericContextProperty("Context1", contextTypeInternet, true, true, false),
+	}
+	s.contexts = append(s.contexts, context0)
+
+	context1 := OfonoContext{
+		ObjectPath: "/ril_0/context1",
+		Properties: makeGenericContextProperty("Context1", contextTypeMMS, false, true, false),
+	}
+	s.contexts = append(s.contexts, context1)
+
+	context2 := OfonoContext{
+		ObjectPath: "/ril_0/context2",
+		Properties: makeGenericContextProperty("Context2", contextTypeMMS, false, true, false),
+	}
+	s.contexts = append(s.contexts, context2)
+
+	context3 := OfonoContext{
+		ObjectPath: "/ril_0/context3",
+		Properties: makeGenericContextProperty("Context3", contextTypeMMS, false, true, false),
+	}
+
+	s.contexts = append(s.contexts, context3)
+
+	contexts, err := s.modem.GetMMSContexts("/ril_0/context3")
+	c.Assert(err, IsNil)
+	c.Assert(len(contexts), Equals, 4)
+	c.Check(contexts[0], DeepEquals, context3)
+	c.Check(contexts[1], DeepEquals, context0)
+	c.Check(contexts[2], DeepEquals, context1)
+	c.Check(contexts[3], DeepEquals, context2)
+}
+
+func (s *ContextTestSuite) TestGetProxy(c *C) {
+	context := OfonoContext{
+		ObjectPath: "/ril_0/context1",
+		Properties: makeGenericContextProperty("Context1", contextTypeInternet, true, true, true),
+	}
+
+	p, err := context.GetProxy()
+	c.Assert(err, IsNil)
+	c.Check(p, DeepEquals, proxy)
+}
+
+func (s *ContextTestSuite) TestGetProxyNoProxy(c *C) {
+	context := OfonoContext{
+		ObjectPath: "/ril_0/context1",
+		Properties: makeGenericContextProperty("Context1", contextTypeInternet, true, true, false),
+	}
+
+	p, err := context.GetProxy()
+	c.Assert(err, IsNil)
+	c.Check(p, DeepEquals, ProxyInfo{})
+}
+
+func (s *ContextTestSuite) TestGetProxyWithHTTP(c *C) {
+	context := OfonoContext{
+		ObjectPath: "/ril_0/context1",
+		Properties: makeGenericContextProperty("Context1", contextTypeInternet, true, true, true),
+	}
+	context.Properties["MessageProxy"] = dbus.Variant{fmt.Sprintf("http://%s:%d", proxy.Host, proxy.Port)}
+
+	p, err := context.GetProxy()
+	c.Assert(err, IsNil)
+	c.Check(p, DeepEquals, proxy)
+}
+
+func (s *ContextTestSuite) TestGetProxyNoPort(c *C) {
+	context := OfonoContext{
+		ObjectPath: "/ril_0/context1",
+		Properties: makeGenericContextProperty("Context1", contextTypeInternet, true, true, true),
+	}
+	context.Properties["MessageProxy"] = dbus.Variant{fmt.Sprintf("http://%s", proxy.Host)}
+
+	p, err := context.GetProxy()
+	c.Assert(err, IsNil)
+	c.Check(p, DeepEquals, ProxyInfo{Host: proxy.Host, Port: 80})
+}

=== added file 'ofono/manager.go'
--- ofono/manager.go	1970-01-01 00:00:00 +0000
+++ ofono/manager.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of nuntium.
+ *
+ * nuntium is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * nuntium 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package ofono
+
+import (
+	"log"
+
+	"launchpad.net/go-dbus/v1"
+)
+
+type Modems map[dbus.ObjectPath]*Modem
+
+type ModemManager struct {
+	ModemAdded   chan (*Modem)
+	ModemRemoved chan (*Modem)
+	modems       Modems
+	conn         *dbus.Connection
+}
+
+func NewModemManager(conn *dbus.Connection) *ModemManager {
+	return &ModemManager{
+		conn:         conn,
+		ModemAdded:   make(chan *Modem),
+		ModemRemoved: make(chan *Modem),
+		modems:       make(Modems),
+	}
+}
+
+func (mm *ModemManager) Init() error {
+	modemAddedSignal, err := connectToSignal(mm.conn, "/", OFONO_MANAGER_INTERFACE, "ModemAdded")
+	if err != nil {
+		return err
+	}
+	modemRemovedSignal, err := connectToSignal(mm.conn, "/", OFONO_MANAGER_INTERFACE, "ModemRemoved")
+	if err != nil {
+		return err
+	}
+	go mm.watchModems(modemAddedSignal, modemRemovedSignal)
+
+	//Check for existing modems
+	modemPaths, err := getModems(mm.conn)
+	if err != nil {
+		log.Print("Cannot preemptively add modems: ", err)
+	} else {
+		for _, objectPath := range modemPaths {
+			mm.addModem(objectPath)
+		}
+	}
+	return nil
+}
+
+func (mm *ModemManager) watchModems(modemAdded, modemRemoved *dbus.SignalWatch) {
+	for {
+		var objectPath dbus.ObjectPath
+		select {
+		case m := <-modemAdded.C:
+			var signalProps PropertiesType
+			if err := m.Args(&objectPath, &signalProps); err != nil {
+				log.Print(err)
+				continue
+			}
+			mm.addModem(objectPath)
+		case m := <-modemRemoved.C:
+			if err := m.Args(&objectPath); err != nil {
+				log.Print(err)
+				continue
+			}
+			mm.removeModem(objectPath)
+		}
+	}
+}
+
+func (mm *ModemManager) addModem(objectPath dbus.ObjectPath) {
+	if modem, ok := mm.modems[objectPath]; ok {
+		log.Print("Need to delete stale modem instance %s", modem.Modem)
+		modem.Delete()
+		delete(mm.modems, objectPath)
+	}
+	mm.modems[objectPath] = NewModem(mm.conn, objectPath)
+	mm.ModemAdded <- mm.modems[objectPath]
+}
+
+func (mm *ModemManager) removeModem(objectPath dbus.ObjectPath) {
+	if modem, ok := mm.modems[objectPath]; ok {
+		mm.ModemRemoved <- mm.modems[objectPath]
+		log.Printf("Deleting modem instance %s", modem.Modem)
+		modem.Delete()
+		delete(mm.modems, objectPath)
+	} else {
+		log.Printf("Cannot satisfy request to remove modem %s as it does not exist", objectPath)
+	}
+}

=== added file 'ofono/modem.go'
--- ofono/modem.go	1970-01-01 00:00:00 +0000
+++ ofono/modem.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,428 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of nuntium.
+ *
+ * nuntium is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * nuntium 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package ofono
+
+import (
+	"errors"
+	"fmt"
+	"log"
+	"net"
+	"reflect"
+	"strconv"
+	"strings"
+	"time"
+
+	"launchpad.net/go-dbus/v1"
+)
+
+const (
+	contextTypeInternet = "internet"
+	contextTypeMMS      = "mms"
+)
+
+const (
+	ofonoAttachInProgressError = "org.ofono.AttachInProgress"
+	ofonoFailed                = "org.ofono.Error.Failed"
+	ofonoInProgressError       = "org.ofono.InProgress"
+	ofonoNotAttachedError      = "org.ofono.Error.NotAttached"
+)
+
+type OfonoContext struct {
+	ObjectPath dbus.ObjectPath
+	Properties PropertiesType
+}
+
+type Modem struct {
+	conn                   *dbus.Connection
+	Modem                  dbus.ObjectPath
+	PushAgent              *PushAgent
+	identity               string
+	IdentityAdded          chan string
+	IdentityRemoved        chan string
+	endWatch               chan bool
+	PushInterfaceAvailable chan bool
+	pushInterfaceAvailable bool
+	online                 bool
+	modemSignal, simSignal *dbus.SignalWatch
+}
+
+type ProxyInfo struct {
+	Host string
+	Port uint64
+}
+
+func (p ProxyInfo) String() string {
+	return fmt.Sprintf("%s:%d", p.Host, p.Port)
+}
+
+func (oProp OfonoContext) String() string {
+	var s string
+	s += fmt.Sprintf("ObjectPath: %s\n", oProp.ObjectPath)
+	for k, v := range oProp.Properties {
+		s += fmt.Sprint("\t", k, ": ", v.Value, "\n")
+	}
+	return s
+}
+
+func NewModem(conn *dbus.Connection, objectPath dbus.ObjectPath) *Modem {
+	return &Modem{
+		conn:                   conn,
+		Modem:                  objectPath,
+		IdentityAdded:          make(chan string),
+		IdentityRemoved:        make(chan string),
+		PushInterfaceAvailable: make(chan bool),
+		endWatch:               make(chan bool),
+		PushAgent:              NewPushAgent(objectPath),
+	}
+}
+
+func (modem *Modem) Init() (err error) {
+	log.Printf("Initializing modem %s", modem.Modem)
+	modem.modemSignal, err = connectToPropertySignal(modem.conn, modem.Modem, MODEM_INTERFACE)
+	if err != nil {
+		return err
+	}
+
+	modem.simSignal, err = connectToPropertySignal(modem.conn, modem.Modem, SIM_MANAGER_INTERFACE)
+	if err != nil {
+		return err
+	}
+
+	// the calling order here avoids race conditions
+	go modem.watchStatus()
+	modem.fetchExistingStatus()
+
+	return nil
+}
+
+// fetchExistingStatus fetches key required for the modem to be considered operational
+// from a push notification point of view
+//
+// status updates are fetched through dbus method calls
+func (modem *Modem) fetchExistingStatus() {
+	if v, err := modem.getProperty(MODEM_INTERFACE, "Interfaces"); err == nil {
+		modem.updatePushInterfaceState(*v)
+	} else {
+		log.Print("Initial value couldn't be retrieved: ", err)
+	}
+	if v, err := modem.getProperty(MODEM_INTERFACE, "Online"); err == nil {
+		modem.handleOnlineState(*v)
+	} else {
+		log.Print("Initial value couldn't be retrieved: ", err)
+	}
+	if v, err := modem.getProperty(SIM_MANAGER_INTERFACE, "SubscriberIdentity"); err == nil {
+		modem.handleIdentity(*v)
+	}
+}
+
+// watchStatus monitors key states required for the modem to be considered operational
+// from a push notification point of view
+//
+// status updates are monitered by hooking up to dbus signals
+func (modem *Modem) watchStatus() {
+	var propName string
+	var propValue dbus.Variant
+watchloop:
+	for {
+		select {
+		case <-modem.endWatch:
+			log.Printf("Ending modem watch for %s", modem.Modem)
+			break watchloop
+		case msg, ok := <-modem.modemSignal.C:
+			if !ok {
+				modem.modemSignal.C = nil
+				continue watchloop
+			}
+			if err := msg.Args(&propName, &propValue); err != nil {
+				log.Printf("Cannot interpret Modem Property change: %s", err)
+				continue watchloop
+			}
+			switch propName {
+			case "Interfaces":
+				modem.updatePushInterfaceState(propValue)
+			case "Online":
+				modem.handleOnlineState(propValue)
+			default:
+				continue watchloop
+			}
+		case msg, ok := <-modem.simSignal.C:
+			if !ok {
+				modem.simSignal.C = nil
+				continue watchloop
+			}
+			if err := msg.Args(&propName, &propValue); err != nil {
+				log.Printf("Cannot interpret Sim Property change: %s", err)
+				continue watchloop
+			}
+			if propName != "SubscriberIdentity" {
+				continue watchloop
+			}
+			modem.handleIdentity(propValue)
+		}
+	}
+}
+
+func (modem *Modem) handleOnlineState(propValue dbus.Variant) {
+	origState := modem.online
+	modem.online = reflect.ValueOf(propValue.Value).Bool()
+	if modem.online != origState {
+		log.Printf("Modem online: %t", modem.online)
+	}
+}
+
+func (modem *Modem) handleIdentity(propValue dbus.Variant) {
+	identity := reflect.ValueOf(propValue.Value).String()
+	if identity == "" && modem.identity != "" {
+		log.Printf("Identity before remove %s", modem.identity)
+
+		modem.IdentityRemoved <- identity
+		modem.identity = identity
+	}
+	log.Printf("Identity added %s", identity)
+	if identity != "" && modem.identity == "" {
+		modem.identity = identity
+		modem.IdentityAdded <- identity
+	}
+}
+
+func (modem *Modem) updatePushInterfaceState(interfaces dbus.Variant) {
+	origState := modem.pushInterfaceAvailable
+	availableInterfaces := reflect.ValueOf(interfaces.Value)
+	for i := 0; i < availableInterfaces.Len(); i++ {
+		interfaceName := reflect.ValueOf(availableInterfaces.Index(i).Interface().(string)).String()
+		if interfaceName == PUSH_NOTIFICATION_INTERFACE {
+			modem.pushInterfaceAvailable = true
+			break
+		}
+	}
+	if modem.pushInterfaceAvailable != origState {
+		log.Printf("Push interface state: %t", modem.pushInterfaceAvailable)
+		if modem.pushInterfaceAvailable {
+			modem.PushInterfaceAvailable <- true
+		} else if modem.PushAgent.Registered {
+			modem.PushInterfaceAvailable <- false
+		}
+	}
+}
+
+var getOfonoProps = func(conn *dbus.Connection, objectPath dbus.ObjectPath, destination, iface, method string) (oProps []OfonoContext, err error) {
+	obj := conn.Object(destination, objectPath)
+	reply, err := obj.Call(iface, method)
+	if err != nil || reply.Type == dbus.TypeError {
+		return oProps, err
+	}
+	if err := reply.Args(&oProps); err != nil {
+		return oProps, err
+	}
+	return oProps, err
+}
+
+//ActivateMMSContext activates a context if necessary and returns the context
+//to operate with MMS.
+//
+//If the context is already active it's a nop.
+//Returns either the type=internet context or the type=mms, if none is found
+//an error is returned.
+func (modem *Modem) ActivateMMSContext(preferredContext dbus.ObjectPath) (OfonoContext, error) {
+	contexts, err := modem.GetMMSContexts(preferredContext)
+	if err != nil {
+		return OfonoContext{}, err
+	}
+	for _, context := range contexts {
+		if context.isActive() {
+			return context, nil
+		}
+		if err := context.toggleActive(true, modem.conn); err == nil {
+			return context, nil
+		} else {
+			log.Println("Failed to activate for", context.ObjectPath, ":", err)
+		}
+	}
+	return OfonoContext{}, errors.New("no context available to activate")
+}
+
+//DeactivateMMSContext deactivates the context if it is of type mms
+func (modem *Modem) DeactivateMMSContext(context OfonoContext) error {
+	if context.isTypeInternet() {
+		return nil
+	}
+
+	return context.toggleActive(false, modem.conn)
+}
+
+func activationErrorNeedsWait(err error) bool {
+	if dbusErr, ok := err.(*dbus.Error); ok {
+		return dbusErr.Name == ofonoInProgressError || dbusErr.Name == ofonoAttachInProgressError || dbusErr.Name == ofonoNotAttachedError || dbusErr.Name == ofonoFailed
+	}
+	return false
+}
+
+func (context OfonoContext) toggleActive(state bool, conn *dbus.Connection) error {
+	log.Println("Trying to set Active property to", state, "for context on", state, context.ObjectPath)
+	obj := conn.Object("org.ofono", context.ObjectPath)
+	for i := 0; i < 3; i++ {
+		_, err := obj.Call(CONNECTION_CONTEXT_INTERFACE, "SetProperty", "Active", dbus.Variant{state})
+		if err != nil {
+			log.Printf("Cannot set Activate to %t (try %d/3) interface on %s: %s", state, i+1, context.ObjectPath, err)
+			if activationErrorNeedsWait(err) {
+				time.Sleep(5 * time.Second)
+			}
+		} else {
+			return nil
+		}
+	}
+	return errors.New("failed to activate context")
+}
+
+func (oContext OfonoContext) isTypeInternet() bool {
+	if v, ok := oContext.Properties["Type"]; ok {
+		return reflect.ValueOf(v.Value).String() == contextTypeInternet
+	}
+	return false
+}
+
+func (oContext OfonoContext) isTypeMMS() bool {
+	if v, ok := oContext.Properties["Type"]; ok {
+		return reflect.ValueOf(v.Value).String() == contextTypeMMS
+	}
+	return false
+}
+
+func (oContext OfonoContext) isActive() bool {
+	return reflect.ValueOf(oContext.Properties["Active"].Value).Bool()
+}
+
+func (oContext OfonoContext) hasMessageCenter() bool {
+	return oContext.messageCenter() != ""
+}
+
+func (oContext OfonoContext) messageCenter() string {
+	if v, ok := oContext.Properties["MessageCenter"]; ok {
+		return reflect.ValueOf(v.Value).String()
+	}
+	return ""
+}
+
+func (oContext OfonoContext) messageProxy() string {
+	if v, ok := oContext.Properties["MessageProxy"]; ok {
+		return reflect.ValueOf(v.Value).String()
+	}
+	return ""
+}
+
+func (oContext OfonoContext) name() string {
+	if v, ok := oContext.Properties["Name"]; ok {
+		return reflect.ValueOf(v.Value).String()
+	}
+	return ""
+}
+
+func (oContext OfonoContext) GetMessageCenter() (string, error) {
+	if oContext.hasMessageCenter() {
+		return oContext.messageCenter(), nil
+	} else {
+		return "", errors.New("context setting for the Message Center value is empty")
+	}
+}
+
+func (oContext OfonoContext) GetProxy() (proxyInfo ProxyInfo, err error) {
+	proxy := oContext.messageProxy()
+	// we need to support empty proxies
+	if proxy == "" {
+		return proxyInfo, nil
+	}
+	if strings.HasPrefix(proxy, "http://") {
+		proxy = proxy[len("http://"):]
+	}
+	var portString string
+	proxyInfo.Host, portString, err = net.SplitHostPort(proxy)
+	if err != nil {
+		proxyInfo.Host = proxy
+		proxyInfo.Port = 80
+		return proxyInfo, nil
+	}
+	proxyInfo.Port, err = strconv.ParseUint(portString, 0, 64)
+	if err != nil {
+		return proxyInfo, err
+	}
+	return proxyInfo, nil
+}
+
+//GetMMSContexts returns the contexts that are MMS capable; by convention it has
+//been defined that for it to be MMS capable it either has to define a MessageProxy
+//and a MessageCenter within the context.
+//
+//The following rules take place:
+//- check current type=internet context for MessageProxy & MessageCenter;
+//  if they exist and aren't empty AND the context is active, select it as the
+//  context to use for MMS.
+//- otherwise search for type=mms, if found, use it and activate
+//
+//Returns either the type=internet context or the type=mms, if none is found
+//an error is returned.
+func (modem *Modem) GetMMSContexts(preferredContext dbus.ObjectPath) (mmsContexts []OfonoContext, err error) {
+	contexts, err := getOfonoProps(modem.conn, modem.Modem, OFONO_SENDER, CONNECTION_MANAGER_INTERFACE, "GetContexts")
+	if err != nil {
+		return mmsContexts, err
+	}
+
+	for _, context := range contexts {
+		if (context.isTypeInternet() && context.isActive() && context.hasMessageCenter()) || context.isTypeMMS() {
+			if context.ObjectPath == preferredContext || context.isActive() {
+				mmsContexts = append([]OfonoContext{context}, mmsContexts...)
+			} else {
+				mmsContexts = append(mmsContexts, context)
+			}
+		}
+	}
+	if len(mmsContexts) == 0 {
+		log.Printf("non matching contexts:\n %+v", contexts)
+		return mmsContexts, errors.New("No mms contexts found")
+	}
+	return mmsContexts, nil
+}
+
+func (modem *Modem) getProperty(interfaceName, propertyName string) (*dbus.Variant, error) {
+	errorString := "Cannot retrieve %s from %s for %s: %s"
+	rilObj := modem.conn.Object(OFONO_SENDER, modem.Modem)
+	if reply, err := rilObj.Call(interfaceName, "GetProperties"); err == nil {
+		var property PropertiesType
+		if err := reply.Args(&property); err != nil {
+			return nil, fmt.Errorf(errorString, propertyName, interfaceName, modem.Modem, err)
+		}
+		if v, ok := property[propertyName]; ok {
+			return &v, nil
+		}
+		return nil, fmt.Errorf(errorString, propertyName, interfaceName, modem.Modem, "property not found")
+	} else {
+		return nil, fmt.Errorf(errorString, propertyName, interfaceName, modem.Modem, err)
+	}
+}
+
+func (modem *Modem) Delete() {
+	modem.IdentityRemoved <- modem.identity
+	modem.modemSignal.Cancel()
+	modem.modemSignal.C = nil
+	modem.simSignal.Cancel()
+	modem.simSignal.C = nil
+	modem.endWatch <- true
+}

=== added file 'ofono/push.go'
--- ofono/push.go	1970-01-01 00:00:00 +0000
+++ ofono/push.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of nuntium.
+ *
+ * nuntium is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * nuntium 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package ofono
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+
+	"launchpad.net/nuntium/mms"
+)
+
+type PDU byte
+
+type PushPDU struct {
+	HeaderLength                             uint64
+	ContentLength                            uint64
+	ApplicationId, EncodingVersion, PushFlag byte
+	ContentType                              string
+	Data                                     []byte
+}
+
+type PushPDUDecoder struct {
+	mms.MMSDecoder
+}
+
+func NewDecoder(data []byte) *PushPDUDecoder {
+	decoder := new(PushPDUDecoder)
+	decoder.MMSDecoder.Data = data
+	return decoder
+}
+
+// The HeadersLen field specifies the length of the ContentType and Headers fields combined.
+// The ContentType field contains the content type of the data. It conforms to the Content-Type value encoding specified
+// in section 8.4.2.24, “Content type field”.
+// The Headers field contains the push headers.
+// The Data field contains the data pushed from the server. The length of the Data field is determined by the SDU size as
+// provided to and reported from the underlying transport. The Data field starts immediately after the Headers field and
+// ends at the end of the SDU.
+func (dec *PushPDUDecoder) Decode(pdu *PushPDU) (err error) {
+	if PDU(dec.Data[1]) != PUSH {
+		return errors.New(fmt.Sprintf("%x != %x is not a push PDU", PDU(dec.Data[1]), PUSH))
+	}
+	// Move offset +tid +type = +2
+	dec.Offset = 1
+	rValue := reflect.ValueOf(pdu).Elem()
+	if _, err = dec.ReadUintVar(&rValue, "HeaderLength"); err != nil {
+		return err
+	}
+	if err = dec.ReadMediaType(&rValue, "ContentType"); err != nil {
+		return err
+	}
+	dec.Offset++
+	remainHeaders := int(pdu.HeaderLength) - dec.Offset + 3
+	if err = dec.decodeHeaders(pdu, remainHeaders); err != nil {
+		return err
+	}
+	pdu.Data = dec.Data[(pdu.HeaderLength + 3):]
+	return nil
+}
+
+func (dec *PushPDUDecoder) decodeHeaders(pdu *PushPDU, hdrLengthRemain int) error {
+	rValue := reflect.ValueOf(pdu).Elem()
+	var err error
+	for ; dec.Offset < (hdrLengthRemain + dec.Offset); dec.Offset++ {
+		param := dec.Data[dec.Offset] & 0x7F
+		switch param {
+		case X_WAP_APPLICATION_ID:
+			_, err = dec.ReadInteger(&rValue, "ApplicationId")
+		case PUSH_FLAG:
+			_, err = dec.ReadShortInteger(&rValue, "PushFlag")
+		case ENCODING_VERSION:
+			dec.Offset++
+			pdu.EncodingVersion = dec.Data[dec.Offset] & 0x7F
+			dec.Offset++
+		case CONTENT_LENGTH:
+			_, err = dec.ReadInteger(&rValue, "ContentLength")
+		case X_WAP_INITIATOR_URI:
+			var v string
+			v, err = dec.ReadString(nil, "")
+			fmt.Println("Unsaved value decoded:", v)
+		default:
+			err = fmt.Errorf("Unhandled header data %#x @%d", dec.Data[dec.Offset], dec.Offset)
+		}
+		if err != nil {
+			return fmt.Errorf("error while decoding %#x @%d: ", param, dec.Offset, err)
+		} else if pdu.ApplicationId != 0 {
+			return nil
+		}
+
+	}
+	return nil
+}

=== added file 'ofono/push_decode_test.go'
--- ofono/push_decode_test.go	1970-01-01 00:00:00 +0000
+++ ofono/push_decode_test.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@canonical.com
+ *
+ * This file is part of mms.
+ *
+ * mms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * mms 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package ofono
+
+import (
+	"errors"
+	"testing"
+	. "launchpad.net/gocheck"
+	"launchpad.net/nuntium/mms"
+)
+
+type PushDecodeTestSuite struct {
+	pdu *PushPDU
+}
+
+var _ = Suite(&PushDecodeTestSuite{})
+
+func Test(t *testing.T) { TestingT(t) }
+
+func (s *PushDecodeTestSuite) SetUpTest(c *C) {
+	s.pdu = new(PushPDU)
+}
+
+func (s *PushDecodeTestSuite) TestDecodeVodaphoneSpain(c *C) {
+	inputBytes := []byte{
+		0x00, 0x06, 0x26, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+		0x6e, 0x2f, 0x76, 0x6e, 0x64, 0x2e, 0x77, 0x61, 0x70, 0x2e, 0x6d, 0x6d, 0x73,
+		0x2d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x00, 0xaf, 0x84, 0xb4, 0x81,
+		0x8d, 0xdf, 0x8c, 0x82, 0x98, 0x4e, 0x4f, 0x4b, 0x35, 0x43, 0x64, 0x7a, 0x30,
+		0x38, 0x42, 0x41, 0x73, 0x77, 0x61, 0x62, 0x77, 0x55, 0x48, 0x00, 0x8d, 0x90,
+		0x89, 0x18, 0x80, 0x2b, 0x33, 0x34, 0x36, 0x30, 0x30, 0x39, 0x34, 0x34, 0x34,
+		0x36, 0x33, 0x2f, 0x54, 0x59, 0x50, 0x45, 0x3d, 0x50, 0x4c, 0x4d, 0x4e, 0x00,
+		0x8a, 0x80, 0x8e, 0x02, 0x74, 0x00, 0x88, 0x05, 0x81, 0x03, 0x02, 0xa3, 0x00,
+		0x83, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6d, 0x6d, 0x31, 0x66, 0x65,
+		0x31, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x6c, 0x65, 0x74, 0x73, 0x2f, 0x4e, 0x4f,
+		0x4b, 0x35, 0x43, 0x64, 0x7a, 0x30, 0x38, 0x42, 0x41, 0x73, 0x77, 0x61, 0x62,
+		0x77, 0x55, 0x48, 0x00,
+	}
+	dec := NewDecoder(inputBytes)
+	c.Assert(dec.Decode(s.pdu), IsNil)
+
+	c.Check(int(s.pdu.HeaderLength), Equals, 38)
+	c.Check(int(s.pdu.ApplicationId), Equals, mms.PUSH_APPLICATION_ID)
+	c.Check(s.pdu.ContentType, Equals, mms.VND_WAP_MMS_MESSAGE)
+	c.Check(len(s.pdu.Data), Equals, 106)
+}
+
+func (s *PushDecodeTestSuite) TestDecodeTelecomPersonal(c *C) {
+	inputBytes := []byte{
+		0x01, 0x06, 0x26, 0x61, 0x70, 0x70, 0x6C, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+		0x6e, 0x2f, 0x76, 0x6e, 0x64, 0x2e, 0x77, 0x61, 0x70, 0x2e, 0x6d, 0x6d, 0x73,
+		0x2d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x00, 0xaf, 0x84, 0xb4, 0x86,
+		0xc3, 0x95, 0x8c, 0x82, 0x98, 0x6d, 0x30, 0x34, 0x42, 0x4b, 0x6b, 0x73, 0x69,
+		0x6d, 0x30, 0x35, 0x40, 0x6d, 0x6d, 0x73, 0x2e, 0x70, 0x65, 0x72, 0x73, 0x6f,
+		0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x72, 0x00, 0x8d, 0x90,
+		0x89, 0x19, 0x80, 0x2b, 0x35, 0x34, 0x33, 0x35, 0x31, 0x35, 0x39, 0x32, 0x34,
+		0x39, 0x30, 0x36, 0x2f, 0x54, 0x59, 0x50, 0x45, 0x3d, 0x50, 0x4c, 0x4d, 0x4e,
+		0x00, 0x8a, 0x80, 0x8e, 0x02, 0x74, 0x00, 0x88, 0x05, 0x81, 0x03, 0x02, 0xa2,
+		0xff, 0x83, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x37, 0x32, 0x2e,
+		0x32, 0x35, 0x2e, 0x37, 0x2e, 0x31, 0x33, 0x31, 0x2f, 0x3f, 0x6d, 0x65, 0x73,
+		0x73, 0x61, 0x67, 0x65, 0x2d, 0x69, 0x64, 0x3d, 0x6d, 0x30, 0x34, 0x42, 0x4b,
+		0x68, 0x34, 0x33, 0x65, 0x30, 0x33, 0x00,
+	}
+	dec := NewDecoder(inputBytes)
+	c.Assert(dec.Decode(s.pdu), IsNil)
+
+	c.Check(int(s.pdu.HeaderLength), Equals, 38)
+	c.Check(int(s.pdu.ApplicationId), Equals, mms.PUSH_APPLICATION_ID)
+	c.Check(s.pdu.ContentType, Equals, mms.VND_WAP_MMS_MESSAGE)
+	c.Check(len(s.pdu.Data), Equals, 122)
+}
+
+func (s *PushDecodeTestSuite) TestDecodeATTUSA(c *C) {
+	inputBytes := []byte{
+		0x01, 0x06, 0x27, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+		0x6e, 0x2f, 0x76, 0x6e, 0x64, 0x2e, 0x77, 0x61, 0x70, 0x2e, 0x6d, 0x6d, 0x73,
+		0x2d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x00, 0xaf, 0x84, 0x8d, 0x01,
+		0x82, 0xb4, 0x84, 0x8c, 0x82, 0x98, 0x44, 0x32, 0x30, 0x34, 0x30, 0x37, 0x31,
+		0x36, 0x35, 0x36, 0x32, 0x34, 0x36, 0x30, 0x30, 0x30, 0x30, 0x34, 0x30, 0x30,
+		0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x8d, 0x90, 0x89, 0x18, 0x80,
+		0x2b, 0x31, 0x37, 0x37, 0x34, 0x32, 0x37, 0x30, 0x30, 0x36, 0x35, 0x39, 0x2f,
+		0x54, 0x59, 0x50, 0x45, 0x3d, 0x50, 0x4c, 0x4d, 0x4e, 0x00, 0x96, 0x02, 0xea,
+		0x00, 0x8a, 0x80, 0x8e, 0x02, 0x80, 0x00, 0x88, 0x05, 0x81, 0x03, 0x05, 0x46,
+		0x00, 0x83, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x36, 0x36, 0x2e,
+		0x32, 0x31, 0x36, 0x2e, 0x31, 0x36, 0x36, 0x2e, 0x36, 0x37, 0x3a, 0x38, 0x30,
+		0x30, 0x34, 0x2f, 0x30, 0x34, 0x30, 0x37, 0x31, 0x36, 0x35, 0x36, 0x32, 0x34,
+		0x36, 0x30, 0x30, 0x30, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+		0x30, 0x30, 0x00,
+	}
+	dec := NewDecoder(inputBytes)
+	c.Assert(dec.Decode(s.pdu), IsNil)
+
+	c.Check(int(s.pdu.HeaderLength), Equals, 39)
+	c.Check(int(s.pdu.ApplicationId), Equals, mms.PUSH_APPLICATION_ID)
+	c.Check(s.pdu.ContentType, Equals, mms.VND_WAP_MMS_MESSAGE)
+	c.Check(len(s.pdu.Data), Equals, 130)
+}
+
+func (s *PushDecodeTestSuite) TestDecodeSoneraFinland(c *C) {
+	inputBytes := []byte{
+		0x00, 0x06, 0x07, 0xbe, 0xaf, 0x84, 0x8d, 0xf2, 0xb4, 0x81, 0x8c, 0x82, 0x98,
+		0x41, 0x42, 0x73, 0x54, 0x4c, 0x4e, 0x41, 0x4c, 0x41, 0x6d, 0x6d, 0x4e, 0x33,
+		0x77, 0x72, 0x38, 0x32, 0x00, 0x8d, 0x92, 0x89, 0x19, 0x80, 0x2b, 0x33, 0x35,
+		0x38, 0x34, 0x30, 0x37, 0x36, 0x39, 0x34, 0x34, 0x38, 0x34, 0x2f, 0x54, 0x59,
+		0x50, 0x45, 0x3d, 0x50, 0x4c, 0x4d, 0x4e, 0x00, 0x86, 0x81, 0x8a, 0x80, 0x8e,
+		0x03, 0x03, 0x15, 0x85, 0x88, 0x05, 0x81, 0x03, 0x03, 0xf4, 0x7f, 0x83, 0x68,
+		0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6d, 0x6d, 0x73, 0x63, 0x36, 0x31, 0x3a,
+		0x31, 0x30, 0x30, 0x32, 0x31, 0x2f, 0x6d, 0x6d, 0x73, 0x63, 0x2f, 0x36, 0x5f,
+		0x31, 0x3f, 0x41, 0x42, 0x73, 0x54, 0x4c, 0x4e, 0x41, 0x4c, 0x41, 0x6d, 0x6d,
+		0x4e, 0x33, 0x77, 0x72, 0x38, 0x32, 0x00,
+	}
+	dec := NewDecoder(inputBytes)
+	c.Assert(dec.Decode(s.pdu), IsNil)
+
+	c.Check(int(s.pdu.HeaderLength), Equals, 7)
+	c.Check(int(s.pdu.ApplicationId), Equals, mms.PUSH_APPLICATION_ID)
+	c.Check(s.pdu.ContentType, Equals, mms.VND_WAP_MMS_MESSAGE)
+	c.Check(len(s.pdu.Data), Equals, 114)
+}
+
+func (s *PushDecodeTestSuite) TestOperatorWithContentLength(c *C) {
+	inputBytes := []byte{
+		0x01, 0x06, 0x07, 0xbe, 0x8d, 0xf0, 0xaf, 0x84, 0xb4, 0x84, 0x8c, 0x82, 0x98,
+		0x41, 0x78, 0x67, 0x41, 0x6a, 0x45, 0x73, 0x49, 0x47, 0x46, 0x57, 0x45, 0x54,
+		0x45, 0x53, 0x76, 0x41, 0x00, 0x8d, 0x93, 0x89, 0x18, 0x80, 0x2b, 0x33, 0x31,
+		0x36, 0x35, 0x35, 0x35, 0x38, 0x34, 0x34, 0x32, 0x35, 0x2f, 0x54, 0x59, 0x50,
+		0x45, 0x3d, 0x50, 0x4c, 0x4d, 0x4e, 0x00, 0x86, 0x81, 0x8a, 0x80, 0x8e, 0x03,
+		0x01, 0xc5, 0x0d, 0x88, 0x05, 0x81, 0x03, 0x03, 0xf4, 0x80, 0x83, 0x68, 0x74,
+		0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6d, 0x70, 0x2e, 0x6d, 0x6f, 0x62, 0x69, 0x65,
+		0x6c, 0x2e, 0x6b, 0x70, 0x6e, 0x2f, 0x6d, 0x6d, 0x73, 0x63, 0x2f, 0x30, 0x31,
+		0x3f, 0x41, 0x78, 0x67, 0x41, 0x6a, 0x45, 0x73, 0x49, 0x47, 0x46, 0x57, 0x45,
+		0x54, 0x45, 0x53, 0x76, 0x41, 0x00,
+	}
+
+	dec := NewDecoder(inputBytes)
+	c.Assert(dec.Decode(s.pdu), IsNil)
+
+	c.Check(int(s.pdu.HeaderLength), Equals, 7)
+	c.Check(int(s.pdu.ContentLength), Equals, 112)
+	c.Check(int(s.pdu.ApplicationId), Equals, mms.PUSH_APPLICATION_ID)
+	c.Check(s.pdu.ContentType, Equals, mms.VND_WAP_MMS_MESSAGE)
+	c.Check(len(s.pdu.Data), Equals, 113)
+}
+
+func (s *PushDecodeTestSuite) TestDecodeNonPushPDU(c *C) {
+	inputBytes := []byte{
+		0x00, 0x07, 0x07, 0xbe, 0xaf, 0x84, 0x8d, 0xf2, 0xb4, 0x81, 0x8c,
+	}
+	dec := NewDecoder(inputBytes)
+	c.Assert(dec.Decode(s.pdu), DeepEquals, errors.New("7 != 6 is not a push PDU"))
+}

=== added file 'ofono/pushagent.go'
--- ofono/pushagent.go	1970-01-01 00:00:00 +0000
+++ ofono/pushagent.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of nuntium.
+ *
+ * nuntium is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * nuntium 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package ofono
+
+import (
+	"encoding/hex"
+	"fmt"
+	"log"
+	"sync"
+
+	"launchpad.net/go-dbus/v1"
+	"launchpad.net/nuntium/mms"
+)
+
+/*
+ in = "aya{sv}", out = ""
+*/
+type OfonoPushNotification struct {
+	Data []byte
+	Info map[string]*dbus.Variant
+}
+
+type PushAgent struct {
+	conn           *dbus.Connection
+	modem          dbus.ObjectPath
+	Push           chan *PushPDU
+	messageChannel chan *dbus.Message
+	Registered     bool
+	m              sync.Mutex
+}
+
+func NewPushAgent(modem dbus.ObjectPath) *PushAgent {
+	return &PushAgent{modem: modem}
+}
+
+func (agent *PushAgent) Register() (err error) {
+	agent.m.Lock()
+	defer agent.m.Unlock()
+	if agent.conn == nil {
+		if agent.conn, err = dbus.Connect(dbus.SystemBus); err != nil {
+			return err
+		}
+	}
+	if agent.Registered {
+		log.Printf("Agent already registered for %s", agent.modem)
+		return nil
+	}
+	agent.Registered = true
+	log.Print("Registering agent for ", agent.modem, " on path ", AGENT_TAG, " and name ", agent.conn.UniqueName)
+	obj := agent.conn.Object("org.ofono", agent.modem)
+	_, err = obj.Call(PUSH_NOTIFICATION_INTERFACE, "RegisterAgent", AGENT_TAG)
+	if err != nil {
+		return fmt.Errorf("Cannot register agent for %s: %s", agent.modem, err)
+	}
+	agent.Push = make(chan *PushPDU)
+	agent.messageChannel = make(chan *dbus.Message)
+	go agent.watchDBusMethodCalls()
+	agent.conn.RegisterObjectPath(AGENT_TAG, agent.messageChannel)
+	log.Print("Agent Registered for ", agent.modem, " on path ", AGENT_TAG)
+	return nil
+}
+
+func (agent *PushAgent) Unregister() error {
+	agent.m.Lock()
+	defer agent.m.Unlock()
+	if !agent.Registered {
+		log.Print("Agent no registered for %s", agent.modem)
+		return nil
+	}
+	log.Print("Unregistering agent on ", agent.modem)
+	obj := agent.conn.Object("org.ofono", agent.modem)
+	_, err := obj.Call(PUSH_NOTIFICATION_INTERFACE, "UnregisterAgent", AGENT_TAG)
+	if err != nil {
+		log.Print("Unregister failed ", err)
+		return err
+	}
+	agent.release()
+	agent.modem = dbus.ObjectPath("")
+	return nil
+}
+
+func (agent *PushAgent) release() {
+	agent.Registered = false
+	//BUG this seems to not return, but I can't close the channel or panic
+	agent.conn.UnregisterObjectPath(AGENT_TAG)
+	close(agent.Push)
+	agent.Push = nil
+	close(agent.messageChannel)
+	agent.messageChannel = nil
+}
+
+func (agent *PushAgent) watchDBusMethodCalls() {
+	var reply *dbus.Message
+	for msg := range agent.messageChannel {
+		switch {
+		case msg.Interface == PUSH_NOTIFICATION_AGENT_INTERFACE && msg.Member == "ReceiveNotification":
+			reply = agent.notificationReceived(msg)
+		case msg.Interface == PUSH_NOTIFICATION_AGENT_INTERFACE && msg.Member == "Release":
+			log.Printf("Push Agent on %s received Release", agent.modem)
+			reply = dbus.NewMethodReturnMessage(msg)
+			agent.release()
+		default:
+			log.Print("Received unkown method call on", msg.Interface, msg.Member)
+			reply = dbus.NewErrorMessage(msg, "org.freedesktop.DBus.Error.UnknownMethod", "Unknown method")
+		}
+		if err := agent.conn.Send(reply); err != nil {
+			log.Print("Could not send reply: ", err)
+		}
+	}
+}
+
+func (agent *PushAgent) notificationReceived(msg *dbus.Message) (reply *dbus.Message) {
+	var push OfonoPushNotification
+	if err := msg.Args(&(push.Data), &(push.Info)); err != nil {
+		log.Print("Error in received ReceiveNotification() method call ", msg)
+		return dbus.NewErrorMessage(msg, "org.freedesktop.DBus.Error", "FormatError")
+	} else {
+		log.Print("Received ReceiveNotification() method call from ", push.Info["Sender"].Value)
+		log.Print("Push data\n", hex.Dump(push.Data))
+		dec := NewDecoder(push.Data)
+		pdu := new(PushPDU)
+		if err := dec.Decode(pdu); err != nil {
+			log.Print("Error ", err)
+			return dbus.NewErrorMessage(msg, "org.freedesktop.DBus.Error", "DecodeError")
+		}
+		// TODO later switch on ApplicationId and ContentType to different channels
+		if pdu.ApplicationId == mms.PUSH_APPLICATION_ID && pdu.ContentType == mms.VND_WAP_MMS_MESSAGE {
+			agent.Push <- pdu
+		} else {
+			log.Print("Unhandled push pdu", pdu)
+		}
+		return dbus.NewMethodReturnMessage(msg)
+	}
+}

=== added file 'ofono/wsp_params.go'
--- ofono/wsp_params.go	1970-01-01 00:00:00 +0000
+++ ofono/wsp_params.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of nuntium.
+ *
+ * nuntium is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * nuntium 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package ofono
+
+//These are the WSP assigned numbers from Table 34. PDU Type Assignments -
+//Appendix A Assigned Numbers in WAP-230-WSP
+const (
+	CONNECT        PDU = 0x01
+	CONNECT_REPLY  PDU = 0x02
+	REDIRECT       PDU = 0x03
+	REPLY          PDU = 0x04
+	DISCONNECT     PDU = 0x05
+	PUSH           PDU = 0x06
+	CONFIRMED_PUSH PDU = 0x07
+	SUSPEND        PDU = 0x08
+	RESUME         PDU = 0x09
+	GET            PDU = 0x40
+	POST           PDU = 0x60
+)
+
+//These are the WSP assigned numbers from Table 38 . Well-Known Parameter
+//Assignments - Appendix A Assigned Numbers in WAP-230-WSP
+const (
+	WSP_PARAMETER_TYPE_Q                  = 0x00
+	WSP_PARAMETER_TYPE_CHARSET            = 0x01
+	WSP_PARAMETER_TYPE_LEVEL              = 0x02
+	WSP_PARAMETER_TYPE_TYPE               = 0x03
+	WSP_PARAMETER_TYPE_NAME_DEFUNCT       = 0x05
+	WSP_PARAMETER_TYPE_FILENAME_DEFUNCT   = 0x06
+	WSP_PARAMETER_TYPE_DIFFERENCES        = 0x07
+	WSP_PARAMETER_TYPE_PADDING            = 0x08
+	WSP_PARAMETER_TYPE_CONTENT_TYPE       = 0x09
+	WSP_PARAMETER_TYPE_START_DEFUNCT      = 0x0A
+	WSP_PARAMETER_TYPE_START_INFO_DEFUNCT = 0x0B
+	WSP_PARAMETER_TYPE_COMMENT_DEFUNCT    = 0x0C
+	WSP_PARAMETER_TYPE_DOMAIN_DEFUNCT     = 0x0D
+	WSP_PARAMETER_TYPE_MAX_AGE            = 0x0E
+	WSP_PARAMETER_TYPE_PATH_DEFUNCT       = 0x0F
+	WSP_PARAMETER_TYPE_SECURE             = 0x10
+	WSP_PARAMETER_TYPE_SEC                = 0x11
+	WSP_PARAMETER_TYPE_MAC                = 0x12
+	WSP_PARAMETER_TYPE_CREATION_DATE      = 0x13
+	WSP_PARAMETER_TYPE_MODIFICATION_DATE  = 0x14
+	WSP_PARAMETER_TYPE_READ_DATE          = 0x15
+	WSP_PARAMETER_TYPE_SIZE               = 0x16
+	WSP_PARAMETER_TYPE_NAME               = 0x17
+	WSP_PARAMETER_TYPE_FILENAME           = 0x18
+	WSP_PARAMETER_TYPE_START              = 0x19
+	WSP_PARAMETER_TYPE_START_INFO         = 0x1A
+	WSP_PARAMETER_TYPE_COMMENT            = 0x1B
+	WSP_PARAMETER_TYPE_DOMAIN             = 0x1C
+	WSP_PARAMETER_TYPE_PATH               = 0x1D
+	WSP_PARAMETER_TYPE_UNTYPED            = 0xFF
+)
+
+//These are the WSP assigned numbers from Table 39 . Header Field Name
+//Assignments - Appendix A Assigned Numbers in WAP-230-WSP
+const (
+	ACCEPT                = 0x00
+	ACCEPT_CHARSET_1      = 0x01
+	ACCEPT_ENCODING_1     = 0x02
+	ACCEPT_LANGUAGE       = 0x03
+	ACCEPT_RANGES         = 0x04
+	AGE                   = 0x05
+	ALLOW                 = 0x06
+	AUTHORIZATION         = 0x07
+	CACHE_CONTROL_1       = 0x08
+	CONNECTION            = 0x09
+	CONTENT_BASE          = 0x0A
+	CONTENT_ENCODING      = 0x0B
+	CONTENT_LANGUAGE      = 0x0C
+	CONTENT_LENGTH        = 0x0D
+	CONTENT_LOCATION      = 0x0E
+	CONTENT_MD5           = 0x0F
+	CONTENT_RANGE_1       = 0x10
+	CONTENT_TYPE          = 0x11
+	DATE                  = 0x12
+	ETAG                  = 0x13
+	EXPIRES               = 0x14
+	FROM                  = 0x15
+	HOST                  = 0x16
+	IF_MODIFIED_SINCE     = 0x17
+	IF_MATCH              = 0x18
+	IF_NONE_MATCH         = 0x19
+	IF_RANGE              = 0x1A
+	IF_UNMODIFIED_SINCE   = 0x1B
+	LOCATION              = 0x1C
+	LAST_MODIFIED         = 0x1D
+	MAX_FORWARDS          = 0x1E
+	PRAGMA                = 0x1F
+	PROXY_AUTHENTICATE    = 0x20
+	PROXY_AUTHORIZATION   = 0x21
+	PUBLIC                = 0x22
+	RANGE                 = 0x23
+	REFERER               = 0x24
+	RETRY_AFTER           = 0x25
+	SERVER                = 0x26
+	TRANSFER_ENCODING     = 0x27
+	UPGRADE               = 0x28
+	USER_AGENT            = 0x29
+	VARY                  = 0x2A
+	VIA                   = 0x2B
+	WARNING               = 0x2C
+	WWW_AUTHENTICATE      = 0x2D
+	CONTENT_DISPOSITION_1 = 0x2E
+	X_WAP_APPLICATION_ID  = 0x2F
+	X_WAP_CONTENT_URI     = 0x30
+	X_WAP_INITIATOR_URI   = 0x31
+	ACCEPT_APPLICATION    = 0x32
+	BEARER_INDICATION     = 0x33
+	PUSH_FLAG             = 0x34
+	PROFILE               = 0x35
+	PROFILE_DIFF          = 0x36
+	PROFILE_WARNING_1     = 0x37
+	EXPECT                = 0x38
+	TE                    = 0x39
+	TRAILER               = 0x3A
+	ACCEPT_CHARSET        = 0x3B
+	ACCEPT_ENCODING       = 0x3C
+	CACHE_CONTROL_2       = 0x3D
+	CONTENT_RANGE         = 0x3E
+	X_WAP_TOD             = 0x3F
+	CONTENT_ID            = 0x40
+	SET_COOKIE            = 0x41
+	COOKIE                = 0x42
+	ENCODING_VERSION      = 0x43
+	PROFILE_WARNING       = 0x44
+	CONTENT_DISPOSITION   = 0x45
+	X_WAP_SECURITY        = 0x46
+	CACHE_CONTROL         = 0x47
+)

=== added directory 'storage'
=== renamed directory 'storage' => 'storage.moved'
=== added file 'storage/const.go'
--- storage/const.go	1970-01-01 00:00:00 +0000
+++ storage/const.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of telepathy.
+ *
+ * mms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * mms 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package storage
+
+const (
+	NONE          = "none"
+	EXPIRED       = "expired"
+	RETRIEVED     = "retrieved"
+	REJECTED      = "rejected"
+	DEFERRED      = "deferred"
+	INDETERMINATE = "indeterminate"
+	FORWARDED     = "forwarded"
+	UNREACHABLE   = "unreachable"
+)
+
+const (
+	NOTIFICATION = "notification"
+	DOWNLOADED   = "downloaded"
+	RECEIVED     = "received"
+	DRAFT        = "draft"
+	SENT         = "sent"
+)

=== added file 'storage/context.go'
--- storage/context.go	1970-01-01 00:00:00 +0000
+++ storage/context.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of telepathy.
+ *
+ * mms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * mms 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package storage
+
+import (
+	"bufio"
+	"encoding/json"
+	"errors"
+	"os"
+	"path/filepath"
+	"sync"
+
+	"log"
+
+	"launchpad.net/go-dbus/v1"
+	"launchpad.net/go-xdg/v0"
+)
+
+var preferredContextPath string = filepath.Join(filepath.Base(os.Args[0]), "preferredContext")
+
+var contextMutex sync.Mutex
+
+type contextSettingMap map[string]dbus.ObjectPath
+
+func SetPreferredContext(identity string, pcObjectPath dbus.ObjectPath) error {
+	contextMutex.Lock()
+	defer contextMutex.Unlock()
+
+	pcFilePath, err := xdg.Cache.Ensure(preferredContextPath)
+	if err != nil {
+		return err
+	}
+	return writeContext(identity, pcObjectPath, pcFilePath)
+}
+
+func GetPreferredContext(identity string) (pcObjectPath dbus.ObjectPath, err error) {
+	contextMutex.Lock()
+	defer contextMutex.Unlock()
+
+	pcFilePath, err := xdg.Cache.Find(preferredContextPath)
+	if err != nil {
+		return pcObjectPath, err
+	}
+	cs, err := readContext(pcFilePath)
+	if err != nil {
+		return pcObjectPath, err
+	}
+	if p, ok := cs[identity]; ok {
+		return p, nil
+	}
+
+	return pcObjectPath, errors.New("path for identity not found")
+}
+
+func readContext(storePath string) (cs contextSettingMap, err error) {
+	file, err := os.Open(storePath)
+	if err != nil {
+		cs = make(contextSettingMap)
+		return cs, err
+	}
+	jsonReader := json.NewDecoder(file)
+	if err = jsonReader.Decode(&cs); err != nil {
+		cs = make(contextSettingMap)
+	}
+	return cs, err
+}
+
+func writeContext(identity string, pc dbus.ObjectPath, storePath string) error {
+	cs, readErr := readContext(storePath)
+	if readErr != nil {
+		log.Println("Cannot read previous context state")
+	}
+
+	file, err := os.Create(storePath)
+	if err != nil {
+		log.Println(err)
+		return err
+	}
+	defer func() {
+		file.Close()
+		if err != nil {
+			os.Remove(storePath)
+		}
+	}()
+	w := bufio.NewWriter(file)
+	defer w.Flush()
+	cs[identity] = pc
+	jsonWriter := json.NewEncoder(w)
+	if err := jsonWriter.Encode(cs); err != nil {
+		log.Println(err)
+		return err
+	}
+	return nil
+}

=== added file 'storage/mmstate.go'
--- storage/mmstate.go	1970-01-01 00:00:00 +0000
+++ storage/mmstate.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of telepathy.
+ *
+ * mms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * mms 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package storage
+
+//SendInfo is a map where every key is a destination and the value can be any of:
+//
+// - "none": no report has been received yet.
+// - "expired": recipient did not retrieve the MMS before expiration.
+// - "retrieved": MMS successfully retrieved by the recipient.
+// - "rejected": recipient rejected the MMS.
+// - "deferred": recipient decided to retrieve the MMS at a later time.
+// - "indeterminate": cannot determine if the MMS reached its destination.
+// - "forwarded": recipient forwarded the MMS without retrieving it first.
+// - "unreachable": recipient is not reachable.
+type SendInfo map[string]string
+
+//Status represents an MMS' state
+//
+// Id represents the transacion ID for the MMS if using delivery request reports
+//
+// State can be:
+// - "notification": m-Notify.Ind PDU not yet downloaded.
+// - "downloaded": m-Retrieve.Conf PDU downloaded, but not yet acknowledged.
+// - "received": m-Retrieve.Conf PDU downloaded and successfully acknowledged.
+// - "draft": m-Send.Req PDU ready for sending.
+// - "sent": m-Send.Req PDU successfully sent.
+//
+// SendState contains the sent state for each delivered message associated to
+// a particular MMS
+type MMSState struct {
+	Id              string
+	State           string
+	ContentLocation string
+	SendState       SendInfo
+}

=== added file 'storage/storage.go'
--- storage/storage.go	1970-01-01 00:00:00 +0000
+++ storage/storage.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of telepathy.
+ *
+ * mms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * mms 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package storage
+
+import (
+	"bufio"
+	"encoding/json"
+	"os"
+	"path"
+
+	"launchpad.net/go-xdg/v0"
+)
+
+const SUBPATH = "nuntium/store"
+
+func Create(uuid, contentLocation string) error {
+	state := MMSState{
+		State:           NOTIFICATION,
+		ContentLocation: contentLocation,
+	}
+	storePath, err := xdg.Data.Ensure(path.Join(SUBPATH, uuid+".db"))
+	if err != nil {
+		return err
+	}
+	return writeState(state, storePath)
+}
+
+func Destroy(uuid string) error {
+	if storePath, err := xdg.Data.Ensure(path.Join(SUBPATH, uuid+".db")); err == nil {
+		if err := os.Remove(storePath); err != nil {
+			return err
+		}
+	} else {
+		return err
+	}
+	if mmsPath, err := GetMMS(uuid); err == nil {
+		if err := os.Remove(mmsPath); err != nil {
+			return err
+		}
+	} else {
+		return err
+	}
+	return nil
+}
+
+func CreateResponseFile(uuid string) (*os.File, error) {
+	filePath, err := xdg.Cache.Ensure(path.Join(SUBPATH, uuid+".m-notifyresp.ind"))
+	if err != nil {
+		return nil, err
+	}
+	return os.Create(filePath)
+}
+
+func UpdateDownloaded(uuid, filePath string) error {
+	mmsPath, err := xdg.Data.Ensure(path.Join(SUBPATH, uuid+".mms"))
+	if err != nil {
+		return err
+	}
+	if err := os.Rename(filePath, mmsPath); err != nil {
+		//TODO delete file
+		return err
+	}
+	state := MMSState{
+		State: DOWNLOADED,
+	}
+	storePath, err := xdg.Data.Find(path.Join(SUBPATH, uuid+".db"))
+	if err != nil {
+		return err
+	}
+	return writeState(state, storePath)
+}
+
+func UpdateRetrieved(uuid string) error {
+	state := MMSState{
+		State: RETRIEVED,
+	}
+	storePath, err := xdg.Data.Find(path.Join(SUBPATH, uuid+".db"))
+	if err != nil {
+		return err
+	}
+	return writeState(state, storePath)
+}
+
+func CreateSendFile(uuid string) (*os.File, error) {
+	state := MMSState{
+		State: DRAFT,
+	}
+	storePath, err := xdg.Data.Ensure(path.Join(SUBPATH, uuid+".db"))
+	if err != nil {
+		return nil, err
+	}
+	if err := writeState(state, storePath); err != nil {
+		os.Remove(storePath)
+		return nil, err
+	}
+	filePath, err := xdg.Cache.Ensure(path.Join(SUBPATH, uuid+".m-send.req"))
+	if err != nil {
+		return nil, err
+	}
+	return os.Create(filePath)
+}
+
+func GetMMS(uuid string) (string, error) {
+	return xdg.Data.Find(path.Join(SUBPATH, uuid+".mms"))
+}
+
+func writeState(state MMSState, storePath string) error {
+	file, err := os.Create(storePath)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		file.Close()
+		if err != nil {
+			os.Remove(storePath)
+		}
+	}()
+	w := bufio.NewWriter(file)
+	defer w.Flush()
+	jsonWriter := json.NewEncoder(w)
+	if err := jsonWriter.Encode(state); err != nil {
+		return err
+	}
+	return nil
+}

=== added directory 'telepathy'
=== renamed directory 'telepathy' => 'telepathy.moved'
=== added file 'telepathy/const.go'
--- telepathy/const.go	1970-01-01 00:00:00 +0000
+++ telepathy/const.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of telepathy.
+ *
+ * mms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * mms 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package telepathy
+
+const (
+	MMS_DBUS_NAME          = "org.ofono.mms"
+	MMS_DBUS_PATH          = "/org/ofono/mms"
+	MMS_MESSAGE_DBUS_IFACE = "org.ofono.mms.Message"
+	MMS_SERVICE_DBUS_IFACE = "org.ofono.mms.Service"
+	MMS_MANAGER_DBUS_IFACE = "org.ofono.mms.Manager"
+)
+
+const (
+	identityProperty           string = "Identity"
+	useDeliveryReportsProperty string = "UseDeliveryReports"
+	modemObjectPathProperty    string = "ModemObjectPath"
+	messageAddedSignal         string = "MessageAdded"
+	messageRemovedSignal       string = "MessageRemoved"
+	serviceAddedSignal         string = "ServiceAdded"
+	serviceRemovedSignal       string = "ServiceRemoved"
+	preferredContextProperty   string = "PreferredContext"
+	propertyChangedSignal      string = "PropertyChanged"
+	statusProperty             string = "Status"
+)
+
+const (
+	PERMANENT_ERROR = "PermanentError"
+	SENT            = "Sent"
+	TRANSIENT_ERROR = "TransientError"
+)
+
+const (
+	PLMN = "/TYPE=PLMN"
+)

=== added file 'telepathy/manager.go'
--- telepathy/manager.go	1970-01-01 00:00:00 +0000
+++ telepathy/manager.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of telepathy.
+ *
+ * mms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * mms 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package telepathy
+
+import (
+	"fmt"
+	"log"
+
+	"launchpad.net/go-dbus/v1"
+)
+
+type MMSManager struct {
+	conn     *dbus.Connection
+	msgChan  chan *dbus.Message
+	services []*MMSService
+}
+
+func NewMMSManager(conn *dbus.Connection) (*MMSManager, error) {
+	name := conn.RequestName(MMS_DBUS_NAME, dbus.NameFlagDoNotQueue)
+	err := <-name.C
+	if err != nil {
+		return nil, fmt.Errorf("Could not aquire name %s", MMS_DBUS_NAME)
+	}
+
+	log.Printf("Registered %s on bus as %s", conn.UniqueName, name.Name)
+
+	manager := MMSManager{conn: conn, msgChan: make(chan *dbus.Message)}
+	go manager.watchDBusMethodCalls()
+	conn.RegisterObjectPath(MMS_DBUS_PATH, manager.msgChan)
+	return &manager, nil
+}
+
+func (manager *MMSManager) watchDBusMethodCalls() {
+	var reply *dbus.Message
+
+	for msg := range manager.msgChan {
+		switch {
+		case msg.Interface == MMS_MANAGER_DBUS_IFACE && msg.Member == "GetServices":
+			log.Print("Received GetServices()")
+			reply = manager.getServices(msg)
+		default:
+			log.Println("Received unkown method call on", msg.Interface, msg.Member)
+			reply = dbus.NewErrorMessage(msg, "org.freedesktop.DBus.Error.UnknownMethod", "Unknown method")
+		}
+		if err := manager.conn.Send(reply); err != nil {
+			log.Print("Could not send reply: ", err)
+		}
+	}
+}
+
+func (manager *MMSManager) getServices(msg *dbus.Message) *dbus.Message {
+	var payloads []Payload
+	for i, _ := range manager.services {
+		payloads = append(payloads, manager.services[i].payload)
+	}
+	reply := dbus.NewMethodReturnMessage(msg)
+	if err := reply.AppendArgs(payloads); err != nil {
+		log.Print("Cannot parse payload data from services")
+		return dbus.NewErrorMessage(msg, "Error.InvalidArguments", "Cannot parse services")
+	}
+	return reply
+}
+
+func (manager *MMSManager) serviceAdded(payload *Payload) error {
+	log.Print("Service added ", payload.Path)
+	signal := dbus.NewSignalMessage(MMS_DBUS_PATH, MMS_MANAGER_DBUS_IFACE, serviceAddedSignal)
+	if err := signal.AppendArgs(payload.Path, payload.Properties); err != nil {
+		return err
+	}
+	if err := manager.conn.Send(signal); err != nil {
+		return fmt.Errorf("Cannot send ServiceAdded for %s", payload.Path)
+	}
+	return nil
+}
+
+func (manager *MMSManager) AddService(identity string, modemObjPath dbus.ObjectPath, outgoingChannel chan *OutgoingMessage, useDeliveryReports bool) (*MMSService, error) {
+	for i := range manager.services {
+		if manager.services[i].isService(identity) {
+			return manager.services[i], nil
+		}
+	}
+	service := NewMMSService(manager.conn, modemObjPath, identity, outgoingChannel, useDeliveryReports)
+	if err := manager.serviceAdded(&service.payload); err != nil {
+		return &MMSService{}, err
+	}
+	manager.services = append(manager.services, service)
+	return service, nil
+}
+
+func (manager *MMSManager) serviceRemoved(payload *Payload) error {
+	log.Print("Service removed ", payload.Path)
+	signal := dbus.NewSignalMessage(MMS_DBUS_PATH, MMS_MANAGER_DBUS_IFACE, serviceRemovedSignal)
+	if err := signal.AppendArgs(payload.Path); err != nil {
+		return err
+	}
+	if err := manager.conn.Send(signal); err != nil {
+		return fmt.Errorf("Cannot send ServiceRemoved for %s", payload.Path)
+	}
+	return nil
+}
+
+func (manager *MMSManager) RemoveService(identity string) error {
+	for i := range manager.services {
+		if manager.services[i].isService(identity) {
+			manager.serviceRemoved(&manager.services[i].payload)
+			manager.services[i].Close()
+			manager.services = append(manager.services[:i], manager.services[i+1:]...)
+			log.Print("Service left: ", len(manager.services))
+			return nil
+		}
+	}
+	return fmt.Errorf("Cannot find service serving %s", identity)
+}

=== added file 'telepathy/message.go'
--- telepathy/message.go	1970-01-01 00:00:00 +0000
+++ telepathy/message.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of telepathy.
+ *
+ * mms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * mms 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package telepathy
+
+import (
+	"fmt"
+	"log"
+	"sort"
+
+	"launchpad.net/go-dbus/v1"
+)
+
+var validStatus sort.StringSlice
+
+func init() {
+	validStatus = sort.StringSlice{SENT, PERMANENT_ERROR, TRANSIENT_ERROR}
+	sort.Strings(validStatus)
+}
+
+type MessageInterface struct {
+	conn       *dbus.Connection
+	objectPath dbus.ObjectPath
+	msgChan    chan *dbus.Message
+	deleteChan chan dbus.ObjectPath
+	status     string
+}
+
+func NewMessageInterface(conn *dbus.Connection, objectPath dbus.ObjectPath, deleteChan chan dbus.ObjectPath) *MessageInterface {
+	msgInterface := MessageInterface{
+		conn:       conn,
+		objectPath: objectPath,
+		deleteChan: deleteChan,
+		msgChan:    make(chan *dbus.Message),
+		status:     "draft",
+	}
+	go msgInterface.watchDBusMethodCalls()
+	conn.RegisterObjectPath(msgInterface.objectPath, msgInterface.msgChan)
+	return &msgInterface
+}
+
+func (msgInterface *MessageInterface) Close() {
+	close(msgInterface.msgChan)
+	msgInterface.msgChan = nil
+	msgInterface.conn.UnregisterObjectPath(msgInterface.objectPath)
+}
+
+func (msgInterface *MessageInterface) watchDBusMethodCalls() {
+	var reply *dbus.Message
+
+	for msg := range msgInterface.msgChan {
+		if msg.Interface != MMS_MESSAGE_DBUS_IFACE {
+			log.Println("Received unkown method call on", msg.Interface, msg.Member)
+			reply = dbus.NewErrorMessage(msg, "org.freedesktop.DBus.Error.UnknownMethod", "Unknown method")
+			continue
+		}
+		switch msg.Member {
+		case "Delete":
+			reply = dbus.NewMethodReturnMessage(msg)
+			//TODO implement store and forward
+			if err := msgInterface.conn.Send(reply); err != nil {
+				log.Println("Could not send reply:", err)
+			}
+			msgInterface.deleteChan <- msgInterface.objectPath
+		default:
+			log.Println("Received unkown method call on", msg.Interface, msg.Member)
+			reply = dbus.NewErrorMessage(msg, "org.freedesktop.DBus.Error.UnknownMethod", "Unknown method")
+			if err := msgInterface.conn.Send(reply); err != nil {
+				log.Println("Could not send reply:", err)
+			}
+		}
+	}
+}
+
+func (msgInterface *MessageInterface) StatusChanged(status string) error {
+	i := validStatus.Search(status)
+	if i < validStatus.Len() && validStatus[i] == status {
+		msgInterface.status = status
+		signal := dbus.NewSignalMessage(msgInterface.objectPath, MMS_MESSAGE_DBUS_IFACE, propertyChangedSignal)
+		if err := signal.AppendArgs(statusProperty, dbus.Variant{status}); err != nil {
+			return err
+		}
+		if err := msgInterface.conn.Send(signal); err != nil {
+			return err
+		}
+		log.Print("Status changed for ", msgInterface.objectPath, " to ", status)
+		return nil
+	}
+	return fmt.Errorf("status %s is not a valid status", status)
+}
+
+func (msgInterface *MessageInterface) GetPayload() *Payload {
+	properties := make(map[string]dbus.Variant)
+	properties["Status"] = dbus.Variant{msgInterface.status}
+	return &Payload{
+		Path:       msgInterface.objectPath,
+		Properties: properties,
+	}
+}

=== added file 'telepathy/service.go'
--- telepathy/service.go	1970-01-01 00:00:00 +0000
+++ telepathy/service.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,385 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * Authors:
+ * Sergio Schvezov: sergio.schvezov@cannical.com
+ *
+ * This file is part of telepathy.
+ *
+ * mms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * mms 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package telepathy
+
+import (
+	"errors"
+	"fmt"
+	"log"
+	"path/filepath"
+	"reflect"
+	"strings"
+	"time"
+
+	"launchpad.net/go-dbus/v1"
+	"launchpad.net/nuntium/mms"
+	"launchpad.net/nuntium/storage"
+)
+
+//Payload is used to build the dbus messages; this is a workaround as v1 of go-dbus
+//tries to encode and decode private fields.
+type Payload struct {
+	Path       dbus.ObjectPath
+	Properties map[string]dbus.Variant
+}
+
+type MMSService struct {
+	payload         Payload
+	Properties      map[string]dbus.Variant
+	conn            *dbus.Connection
+	msgChan         chan *dbus.Message
+	messageHandlers map[dbus.ObjectPath]*MessageInterface
+	msgDeleteChan   chan dbus.ObjectPath
+	identity        string
+	outMessage      chan *OutgoingMessage
+}
+
+type Attachment struct {
+	Id        string
+	MediaType string
+	FilePath  string
+	Offset    uint64
+	Length    uint64
+}
+
+type OutAttachment struct {
+	Id          string
+	ContentType string
+	FilePath    string
+}
+
+type OutgoingMessage struct {
+	Recipients  []string
+	Attachments []OutAttachment
+	Reply       *dbus.Message
+}
+
+func NewMMSService(conn *dbus.Connection, modemObjPath dbus.ObjectPath, identity string, outgoingChannel chan *OutgoingMessage, useDeliveryReports bool) *MMSService {
+	properties := make(map[string]dbus.Variant)
+	properties[identityProperty] = dbus.Variant{identity}
+	serviceProperties := make(map[string]dbus.Variant)
+	serviceProperties[useDeliveryReportsProperty] = dbus.Variant{useDeliveryReports}
+	serviceProperties[modemObjectPathProperty] = dbus.Variant{modemObjPath}
+	payload := Payload{
+		Path:       dbus.ObjectPath(MMS_DBUS_PATH + "/" + identity),
+		Properties: properties,
+	}
+	service := MMSService{
+		payload:         payload,
+		Properties:      serviceProperties,
+		conn:            conn,
+		msgChan:         make(chan *dbus.Message),
+		msgDeleteChan:   make(chan dbus.ObjectPath),
+		messageHandlers: make(map[dbus.ObjectPath]*MessageInterface),
+		outMessage:      outgoingChannel,
+		identity:        identity,
+	}
+	go service.watchDBusMethodCalls()
+	go service.watchMessageDeleteCalls()
+	conn.RegisterObjectPath(payload.Path, service.msgChan)
+	return &service
+}
+
+func (service *MMSService) watchMessageDeleteCalls() {
+	for msgObjectPath := range service.msgDeleteChan {
+		if err := service.MessageRemoved(msgObjectPath); err != nil {
+			log.Print("Failed to delete ", msgObjectPath, ": ", err)
+		}
+	}
+}
+
+func (service *MMSService) watchDBusMethodCalls() {
+	for msg := range service.msgChan {
+		var reply *dbus.Message
+		if msg.Interface != MMS_SERVICE_DBUS_IFACE {
+			log.Println("Received unkown method call on", msg.Interface, msg.Member)
+			reply = dbus.NewErrorMessage(
+				msg,
+				"org.freedesktop.DBus.Error.UnknownInterface",
+				fmt.Sprintf("No such interface '%s' at object path '%s'", msg.Interface, msg.Path))
+			continue
+		}
+		switch msg.Member {
+		case "GetMessages":
+			reply = dbus.NewMethodReturnMessage(msg)
+			//TODO implement store and forward
+			var payload []Payload
+			if err := reply.AppendArgs(payload); err != nil {
+				log.Print("Cannot parse payload data from services")
+				reply = dbus.NewErrorMessage(msg, "Error.InvalidArguments", "Cannot parse services")
+			}
+			if err := service.conn.Send(reply); err != nil {
+				log.Println("Could not send reply:", err)
+			}
+		case "GetProperties":
+			reply = dbus.NewMethodReturnMessage(msg)
+			if pc, err := service.GetPreferredContext(); err == nil {
+				service.Properties[preferredContextProperty] = dbus.Variant{pc}
+			} else {
+				// Using "/" as an invalid 'path' even though it could be considered 'incorrect'
+				service.Properties[preferredContextProperty] = dbus.Variant{dbus.ObjectPath("/")}
+			}
+			if err := reply.AppendArgs(service.Properties); err != nil {
+				log.Print("Cannot parse payload data from services")
+				reply = dbus.NewErrorMessage(msg, "Error.InvalidArguments", "Cannot parse services")
+			}
+			if err := service.conn.Send(reply); err != nil {
+				log.Println("Could not send reply:", err)
+			}
+		case "SetProperty":
+			if err := service.setProperty(msg); err != nil {
+				log.Println("Property set failed:", err)
+				reply = dbus.NewErrorMessage(msg, "Error.InvalidArguments", err.Error())
+			} else {
+				reply = dbus.NewMethodReturnMessage(msg)
+			}
+			if err := service.conn.Send(reply); err != nil {
+				log.Println("Could not send reply:", err)
+			}
+		case "SendMessage":
+			var outMessage OutgoingMessage
+			outMessage.Reply = dbus.NewMethodReturnMessage(msg)
+			if err := msg.Args(&outMessage.Recipients, &outMessage.Attachments); err != nil {
+				log.Print("Cannot parse payload data from services")
+				reply = dbus.NewErrorMessage(msg, "Error.InvalidArguments", "Cannot parse New Message")
+				if err := service.conn.Send(reply); err != nil {
+					log.Println("Could not send reply:", err)
+				}
+			} else {
+				service.outMessage <- &outMessage
+			}
+		default:
+			log.Println("Received unkown method call on", msg.Interface, msg.Member)
+			reply = dbus.NewErrorMessage(
+				msg,
+				"org.freedesktop.DBus.Error.UnknownMethod",
+				fmt.Sprintf("No such method '%s' at object path '%s'", msg.Member, msg.Path))
+			if err := service.conn.Send(reply); err != nil {
+				log.Println("Could not send reply:", err)
+			}
+		}
+	}
+}
+
+func getUUIDFromObjectPath(objectPath dbus.ObjectPath) (string, error) {
+	str := string(objectPath)
+	defaultError := fmt.Errorf("%s is not a proper object path for a Message", str)
+	if str == "" {
+		return "", defaultError
+	}
+	uuid := filepath.Base(str)
+	if uuid == "" || uuid == ".." || uuid == "." {
+		return "", defaultError
+	}
+	return uuid, nil
+}
+
+func (service *MMSService) SetPreferredContext(context dbus.ObjectPath) error {
+	// make set a noop if we are setting the same thing
+	if pc, err := service.GetPreferredContext(); err != nil && context == pc {
+		return nil
+	}
+
+	if err := storage.SetPreferredContext(service.identity, context); err != nil {
+		return err
+	}
+	signal := dbus.NewSignalMessage(service.payload.Path, MMS_SERVICE_DBUS_IFACE, propertyChangedSignal)
+	if err := signal.AppendArgs(preferredContextProperty, dbus.Variant{context}); err != nil {
+		return err
+	}
+	return service.conn.Send(signal)
+}
+
+func (service *MMSService) GetPreferredContext() (dbus.ObjectPath, error) {
+	return storage.GetPreferredContext(service.identity)
+}
+
+func (service *MMSService) setProperty(msg *dbus.Message) error {
+	var propertyName string
+	var propertyValue dbus.Variant
+	if err := msg.Args(&propertyName, &propertyValue); err != nil {
+		return err
+	}
+
+	switch propertyName {
+	case preferredContextProperty:
+		preferredContextObjectPath := dbus.ObjectPath(reflect.ValueOf(propertyValue.Value).String())
+		service.Properties[preferredContextProperty] = dbus.Variant{preferredContextObjectPath}
+		return service.SetPreferredContext(preferredContextObjectPath)
+	default:
+		errors.New("property cannot be set")
+	}
+	return errors.New("unhandled property")
+}
+
+//MessageRemoved emits the MessageRemoved signal with the path of the removed
+//message.
+//It also actually removes the message from storage.
+func (service *MMSService) MessageRemoved(objectPath dbus.ObjectPath) error {
+	service.messageHandlers[objectPath].Close()
+	delete(service.messageHandlers, objectPath)
+
+	uuid, err := getUUIDFromObjectPath(objectPath)
+	if err != nil {
+		return err
+	}
+	if err := storage.Destroy(uuid); err != nil {
+		return err
+	}
+
+	signal := dbus.NewSignalMessage(service.payload.Path, MMS_SERVICE_DBUS_IFACE, messageRemovedSignal)
+	if err := signal.AppendArgs(objectPath); err != nil {
+		return err
+	}
+	if err := service.conn.Send(signal); err != nil {
+		return err
+	}
+	return nil
+}
+
+//IncomingMessageAdded emits a MessageAdded with the path to the added message which
+//is taken as a parameter and creates an object path on the message interface.
+func (service *MMSService) IncomingMessageAdded(mRetConf *mms.MRetrieveConf) error {
+	payload, err := service.parseMessage(mRetConf)
+	if err != nil {
+		return err
+	}
+	service.messageHandlers[payload.Path] = NewMessageInterface(service.conn, payload.Path, service.msgDeleteChan)
+	return service.MessageAdded(&payload)
+}
+
+//MessageAdded emits a MessageAdded with the path to the added message which
+//is taken as a parameter
+func (service *MMSService) MessageAdded(msgPayload *Payload) error {
+	signal := dbus.NewSignalMessage(service.payload.Path, MMS_SERVICE_DBUS_IFACE, messageAddedSignal)
+	if err := signal.AppendArgs(msgPayload.Path, msgPayload.Properties); err != nil {
+		return err
+	}
+	return service.conn.Send(signal)
+}
+
+func (service *MMSService) isService(identity string) bool {
+	path := dbus.ObjectPath(MMS_DBUS_PATH + "/" + identity)
+	if path == service.payload.Path {
+		return true
+	}
+	return false
+}
+
+func (service *MMSService) Close() {
+	service.conn.UnregisterObjectPath(service.payload.Path)
+	close(service.msgChan)
+	close(service.msgDeleteChan)
+}
+
+func (service *MMSService) parseMessage(mRetConf *mms.MRetrieveConf) (Payload, error) {
+	params := make(map[string]dbus.Variant)
+	params["Status"] = dbus.Variant{"received"}
+	//TODO retrieve date correctly
+	date := parseDate(mRetConf.Date)
+	params["Date"] = dbus.Variant{date}
+	if mRetConf.Subject != "" {
+		params["Subject"] = dbus.Variant{mRetConf.Subject}
+	}
+	sender := mRetConf.From
+	if strings.HasSuffix(mRetConf.From, PLMN) {
+		params["Sender"] = dbus.Variant{sender[:len(sender)-len(PLMN)]}
+	}
+
+	params["Recipients"] = dbus.Variant{parseRecipients(mRetConf.To)}
+	if smil, err := mRetConf.GetSmil(); err == nil {
+		params["Smil"] = dbus.Variant{smil}
+	} else {
+		return Payload{}, err
+	}
+	var attachments []Attachment
+	dataParts := mRetConf.GetDataParts()
+	for i := range dataParts {
+		var filePath string
+		if f, err := storage.GetMMS(mRetConf.UUID); err == nil {
+			filePath = f
+		} else {
+			return Payload{}, err
+		}
+		attachment := Attachment{
+			Id:        dataParts[i].ContentId,
+			MediaType: dataParts[i].MediaType,
+			FilePath:  filePath,
+			Offset:    uint64(dataParts[i].Offset),
+			Length:    uint64(len(dataParts[i].Data)),
+		}
+		attachments = append(attachments, attachment)
+	}
+	params["Attachments"] = dbus.Variant{attachments}
+	payload := Payload{Path: service.genMessagePath(mRetConf.UUID), Properties: params}
+	return payload, nil
+}
+
+func parseDate(unixTime uint64) string {
+	const layout = "2014-03-30T18:15:30-0300"
+	date := time.Unix(int64(unixTime), 0)
+	return date.Format(time.RFC3339)
+}
+
+func parseRecipients(to string) []string {
+	recipients := strings.Split(to, ",")
+	for i := range recipients {
+		if strings.HasSuffix(recipients[i], PLMN) {
+			recipients[i] = recipients[i][:len(recipients[i])-len(PLMN)]
+		}
+	}
+	return recipients
+}
+
+func (service *MMSService) MessageDestroy(uuid string) error {
+	msgObjectPath := service.genMessagePath(uuid)
+	if msgInterface, ok := service.messageHandlers[msgObjectPath]; ok {
+		msgInterface.Close()
+		delete(service.messageHandlers, msgObjectPath)
+	}
+	return fmt.Errorf("no message interface handler for object path %s", msgObjectPath)
+}
+
+func (service *MMSService) MessageStatusChanged(uuid, status string) error {
+	msgObjectPath := service.genMessagePath(uuid)
+	if msgInterface, ok := service.messageHandlers[msgObjectPath]; ok {
+		return msgInterface.StatusChanged(status)
+	}
+	return fmt.Errorf("no message interface handler for object path %s", msgObjectPath)
+}
+
+func (service *MMSService) ReplySendMessage(reply *dbus.Message, uuid string) (dbus.ObjectPath, error) {
+	msgObjectPath := service.genMessagePath(uuid)
+	reply.AppendArgs(msgObjectPath)
+	if err := service.conn.Send(reply); err != nil {
+		return "", err
+	}
+	msg := NewMessageInterface(service.conn, msgObjectPath, service.msgDeleteChan)
+	service.messageHandlers[msgObjectPath] = msg
+	service.MessageAdded(msg.GetPayload())
+	return msgObjectPath, nil
+}
+
+//TODO randomly creating a uuid until the download manager does this for us
+func (service *MMSService) genMessagePath(uuid string) dbus.ObjectPath {
+	return dbus.ObjectPath(MMS_DBUS_PATH + "/" + service.identity + "/" + uuid)
+}

=== added directory 'test'
=== renamed directory 'test' => 'test.moved'
=== added file 'test/test.go'
--- test/test.go	1970-01-01 00:00:00 +0000
+++ test/test.go	2015-09-02 10:42:28 +0000
@@ -0,0 +1,95 @@
+package main
+
+import (
+	"fmt"
+	"os"
+
+	"launchpad.net/go-dbus/v1"
+)
+
+func main() {
+	var pdu string
+	if len(os.Args) < 2 {
+		fmt.Printf("Usage: %s [busname]\n", os.Args[0])
+		os.Exit(1)
+	} else if len(os.Args) == 3 {
+		pdu = os.Args[2]
+	} else {
+		pdu = "argentina-personal"
+	}
+
+	var err error
+	var conn *dbus.Connection
+	if conn, err = dbus.Connect(dbus.SystemBus); err != nil {
+		fmt.Println("Connection error:", err)
+		os.Exit(1)
+	}
+
+	obj := conn.Object(os.Args[1], "/nuntium")
+
+	var data []byte
+	switch pdu {
+	case "spain-vodaphone":
+		//VodaPhone España
+		data = []byte{
+			0x00, 0x06, 0x26, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+			0x6e, 0x2f, 0x76, 0x6e, 0x64, 0x2e, 0x77, 0x61, 0x70, 0x2e, 0x6d, 0x6d, 0x73,
+			0x2d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x00, 0xaf, 0x84, 0xb4, 0x81,
+			0x8d, 0xdf, 0x8c, 0x82, 0x98, 0x4e, 0x4f, 0x4b, 0x35, 0x43, 0x64, 0x7a, 0x30,
+			0x38, 0x42, 0x41, 0x73, 0x77, 0x61, 0x62, 0x77, 0x55, 0x48, 0x00, 0x8d, 0x90,
+			0x89, 0x18, 0x80, 0x2b, 0x33, 0x34, 0x36, 0x30, 0x30, 0x39, 0x34, 0x34, 0x34,
+			0x36, 0x33, 0x2f, 0x54, 0x59, 0x50, 0x45, 0x3d, 0x50, 0x4c, 0x4d, 0x4e, 0x00,
+			0x8a, 0x80, 0x8e, 0x02, 0x74, 0x00, 0x88, 0x05, 0x81, 0x03, 0x02, 0xa3, 0x00,
+			0x83, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6d, 0x6d, 0x31, 0x66, 0x65,
+			0x31, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x6c, 0x65, 0x74, 0x73, 0x2f, 0x4e, 0x4f,
+			0x4b, 0x35, 0x43, 0x64, 0x7a, 0x30, 0x38, 0x42, 0x41, 0x73, 0x77, 0x61, 0x62,
+			0x77, 0x55, 0x48, 0x00,
+		}
+	case "argentina-personal":
+		//Personal Argentina
+		data = []byte{
+			0x01, 0x06, 0x26, 0x61, 0x70, 0x70, 0x6C, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+			0x6e, 0x2f, 0x76, 0x6e, 0x64, 0x2e, 0x77, 0x61, 0x70, 0x2e, 0x6d, 0x6d, 0x73,
+			0x2d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x00, 0xaf, 0x84, 0xb4, 0x86,
+			0xc3, 0x95, 0x8c, 0x82, 0x98, 0x6d, 0x30, 0x34, 0x42, 0x4b, 0x6b, 0x73, 0x69,
+			0x6d, 0x30, 0x35, 0x40, 0x6d, 0x6d, 0x73, 0x2e, 0x70, 0x65, 0x72, 0x73, 0x6f,
+			0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x72, 0x00, 0x8d, 0x90,
+			0x89, 0x19, 0x80, 0x2b, 0x35, 0x34, 0x33, 0x35, 0x31, 0x35, 0x39, 0x32, 0x34,
+			0x39, 0x30, 0x36, 0x2f, 0x54, 0x59, 0x50, 0x45, 0x3d, 0x50, 0x4c, 0x4d, 0x4e,
+			0x00, 0x8a, 0x80, 0x8e, 0x02, 0x74, 0x00, 0x88, 0x05, 0x81, 0x03, 0x02, 0xa2,
+			0xff, 0x83, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x37, 0x32, 0x2e,
+			0x32, 0x35, 0x2e, 0x37, 0x2e, 0x31, 0x33, 0x31, 0x2f, 0x3f, 0x6d, 0x65, 0x73,
+			0x73, 0x61, 0x67, 0x65, 0x2d, 0x69, 0x64, 0x3d, 0x6d, 0x30, 0x34, 0x42, 0x4b,
+			0x68, 0x34, 0x33, 0x65, 0x30, 0x33, 0x00,
+		}
+	case "usa-att":
+		//USA AT&T
+		data = []byte{
+			0x01, 0x06, 0x27, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+			0x6e, 0x2f, 0x76, 0x6e, 0x64, 0x2e, 0x77, 0x61, 0x70, 0x2e, 0x6d, 0x6d, 0x73,
+			0x2d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x00, 0xaf, 0x84, 0x8d, 0x01,
+			0x82, 0xb4, 0x84, 0x8c, 0x82, 0x98, 0x44, 0x32, 0x30, 0x34, 0x30, 0x37, 0x31,
+			0x36, 0x35, 0x36, 0x32, 0x34, 0x36, 0x30, 0x30, 0x30, 0x30, 0x34, 0x30, 0x30,
+			0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x8d, 0x90, 0x89, 0x18, 0x80,
+			0x2b, 0x31, 0x37, 0x37, 0x34, 0x32, 0x37, 0x30, 0x30, 0x36, 0x35, 0x39, 0x2f,
+			0x54, 0x59, 0x50, 0x45, 0x3d, 0x50, 0x4c, 0x4d, 0x4e, 0x00, 0x96, 0x02, 0xea,
+			0x00, 0x8a, 0x80, 0x8e, 0x02, 0x80, 0x00, 0x88, 0x05, 0x81, 0x03, 0x05, 0x46,
+			0x00, 0x83, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x36, 0x36, 0x2e,
+			0x32, 0x31, 0x36, 0x2e, 0x31, 0x36, 0x36, 0x2e, 0x36, 0x37, 0x3a, 0x38, 0x30,
+			0x30, 0x34, 0x2f, 0x30, 0x34, 0x30, 0x37, 0x31, 0x36, 0x35, 0x36, 0x32, 0x34,
+			0x36, 0x30, 0x30, 0x30, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+			0x30, 0x30, 0x00,
+		}
+	default:
+		fmt.Println("Choose between argentina-personal and spain-vodaphone")
+	}
+
+	info := map[string]*dbus.Variant{"LocalSentTime": &dbus.Variant{"2014-02-05T08:29:55-0300"},
+		"Sender": &dbus.Variant{"+543515924906"}}
+
+	reply, err := obj.Call("org.ofono.PushNotificationAgent", "ReceiveNotification", data, info)
+	if err != nil || reply.Type == dbus.TypeError {
+		fmt.Printf("Notification error: %s", err)
+		os.Exit(1)
+	}
+}

