nuntium-1.4+15.10.20150902/0000755000015300001610000000000012571563531015415 5ustar pbuserpbgroup00000000000000nuntium-1.4+15.10.20150902/.travis.yml0000644000015300001610000000100012571563375017523 0ustar pbuserpbgroup00000000000000language: go go: - 1.2 - tip before_install: - go get github.com/axw/gocov/gocov - go get github.com/mattn/goveralls - if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi script: - COVERALLS="-repotoken $COVERALLS_TOKEN" ./scripts/testcoverage.sh env: - secure: "DygCBexI9tfMqcZoAQsuhhgCtCNRqkEWtigZoal1gVTvDHCFSBbgs3DfwkVgIXITV8Al8KejOWOFRTo2EmTluRuRSiQIFI0DL1cdU/PyxmZxLVK0tF3X8Yh7yTEMxpcl3jJf+8AVIHzUsspASWcV1qyp70JP0Kgjo81qkbIHPzg=" nuntium-1.4+15.10.20150902/storage/0000755000015300001610000000000012571563531017061 5ustar pbuserpbgroup00000000000000nuntium-1.4+15.10.20150902/storage/context.go0000644000015300001610000000512112571563375021101 0ustar pbuserpbgroup00000000000000/* * 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 . */ 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 } nuntium-1.4+15.10.20150902/storage/const.go0000644000015300001610000000212712571563375020546 0ustar pbuserpbgroup00000000000000/* * 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 . */ 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" ) nuntium-1.4+15.10.20150902/storage/mmstate.go0000644000015300001610000000373212571563375021075 0ustar pbuserpbgroup00000000000000/* * 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 . */ 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 } nuntium-1.4+15.10.20150902/storage/storage.go0000644000015300001610000000625412571563375021071 0ustar pbuserpbgroup00000000000000/* * 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 . */ 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 } nuntium-1.4+15.10.20150902/telepathy/0000755000015300001610000000000012571563531017414 5ustar pbuserpbgroup00000000000000nuntium-1.4+15.10.20150902/telepathy/const.go0000644000015300001610000000317012571563375021100 0ustar pbuserpbgroup00000000000000/* * 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 . */ 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" ) nuntium-1.4+15.10.20150902/telepathy/service.go0000644000015300001610000003054612571563375021421 0ustar pbuserpbgroup00000000000000/* * 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 . */ package telepathy import ( "errors" "fmt" "log" "path/filepath" "reflect" "strings" "time" "github.com/ubuntu-phonedations/nuntium/mms" "github.com/ubuntu-phonedations/nuntium/storage" "launchpad.net/go-dbus/v1" ) //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(strings.Join(mRetConf.To, ","))} if smil, err := mRetConf.GetSmil(); err == nil { params["Smil"] = dbus.Variant{smil} } 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) } nuntium-1.4+15.10.20150902/telepathy/message.go0000644000015300001610000000676112571563375021407 0ustar pbuserpbgroup00000000000000/* * 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 . */ 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, } } nuntium-1.4+15.10.20150902/telepathy/manager.go0000644000015300001610000001030212571563375021357 0ustar pbuserpbgroup00000000000000/* * 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 . */ 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) } nuntium-1.4+15.10.20150902/docs/0000755000015300001610000000000012571563531016345 5ustar pbuserpbgroup00000000000000nuntium-1.4+15.10.20150902/docs/architecture.md0000644000015300001610000000140512571563375021357 0ustar pbuserpbgroup00000000000000# Architecture ## Overview `nuntium` lies in between `ofono` over the system bus and `telepathy-ofono` over the session bus. `nuntium` registers an agent against `ofono`'s `push notification` plugin and sets up to receive DBus method calls from `ofono` when push messages arrive. And it creates an instance on the session to handle method calls from `telepathy-ofono` to send messages and signal message and service events. ### Receiving an MMS This is a simplified scenario for an incoming message with deferral's set to false: ![MMS Retrieval](assets/receiving_success_deferral_disabled.png) ### Sending an MMS This is a simplified scenario for sending a message with message delivery set to false: ![MMS Retrieval](assets/send_success_delivery_disabled.png) nuntium-1.4+15.10.20150902/docs/testing.md0000644000015300001610000000721612571563375020360 0ustar pbuserpbgroup00000000000000# Testing nuntium ## Tools These are generic debugging tools provided as part of nuntium or described as external aids that can be used for troubleshooting. These do not stub components, so are safe for operator testing. ### nuntium-decode-cli This tool allows you to easily decode an *M-Retrieve.conf* displaying the output of what is decoded to stdout with an option to drop the decoded artifacts into a specific path. Install it by running: go get github.com/ubuntu-phonedations/nuntium/cmd/nuntium-decode-cli Refer to the tool's [documentation](http://godoc.org/github.com/ubuntu-phonedations/nuntium/cmd/nuntium-decode-cli) for more information. ### nuntium-preferred-context *Needs implementation* This tool allows reading or writing the preferred context `nuntium` will use when trying to activate a context. Install it by running: go get github.com/ubuntu-phonedations/nuntium/cmd/nuntium-preferred-context Refer to the tool's [documentation](http://godoc.org/github.com/ubuntu-phonedations/nuntium/cmd/nuntium-preferred-context) for more information. ### tcpdump When doing operator testing and MMS debugging is needed, tcpdump can provide the right tools to debug the issue. To get it going, on the device, do: sudo mount -o remount,rw / sudo apt install tcpdump sudo tcpdump -w [file] The capture `[file]` can be analyzed to better understand the problem. ### network-test-session Additionally to `tcpdump`, a useful tool to pinpoint problems is `network-test-session`, refer to the [documentation](https://github.com/sergiusens/network-test-session/blob/master/README.md) for this tool on how to use it. ## Testing tools This section describes a list of external tools to tests and a short summary of what can be done with them. Some of these tools remove the operator from the picture, so in the case of doing operator testing, these really should be avoided. These tools don't yet mock or stub `ofono`'s APN contexts logic but override the logic in code completely if needed. ### nuntium-inject-push This tool is meant to inject a push notification message through the `ReceiveNotification` method nuntium exposes on the system bus and simulate a black box workflow as described in [Receiving an MMS](architecture.md#receiving-an-mms). Install it by running: go get github.com/ubuntu-phonedations/nuntium/cmd/nuntium-inject-push This tool does not mock out ofono completely, but instead what it creates a local server to serve an mms that would be passed on from the Push Notification with a Content Location such that this local server would be used to fetch the MMS. If no MMS is specified a in tool *m-retrieve.conf* would be used. Once the content is served once, the tool exits. The agent is registered on the system bus, as such, it should be run like: sudo nuntium-inject-push --end-point :1.356 where `:1.356` is the dbus name that nuntium has on the system bus, this can be discovered by looking at the nuntium logs. To use a specifc mms, just use the cli switch like: sudo nuntium-inject-push --end-point :1.356 ### nuntium-stub-send *Needs implementation* This tool verifies and simulates sending an MMS simulating what would happen when [Sending an MMS](architecture.md#sending-an-mms) It will track that the correct signals are raised over the bus. Install it by running: go get github.com/ubuntu-phonedations/nuntium/cmd/nuntium-stub-send Refer to the tool's [documentation](http://godoc.org/github.com/ubuntu-phonedations/nuntium/cmd/nuntium-stub-send) for more information. ## Installing on Ubuntu On Ubuntu (vivid+), all of the `nuntium-*` tools can be installed by sudo apt install nuntium-tools nuntium-1.4+15.10.20150902/docs/assets/0000755000015300001610000000000012571563531017647 5ustar pbuserpbgroup00000000000000nuntium-1.4+15.10.20150902/docs/assets/send_success_delivery_disabled.png0000644000015300001610000006230512571563375026604 0ustar pbuserpbgroup00000000000000PNG  IHDRFs\ pHYs+ IDATxkPTW@4H%jc$A AB8eD0LbX$b&8 rӡDH8F 1#jZ 4ih~^zn{˴Zkb3Faա0 P`u(:FX Bprr@?h4i[&l<pW˨kD& Ah4 B&TTo̘1oW^PwO0@a!+++;;mO{{>]'cRNt<׮]oU#_XXJJOOZn]EEҥK{zz1ڈ'Q(,7/^9ҁC+//cÉ. $$LeϞ=!!!#رqʕ[jաC***F:4X x4͚5k׬Y#-qKFHݾW^yEP޽Ę0 /lmm]eAvkb,YdܸqO??{lܸqo{A:p@``K``ҿ>Rf{jkk+++}||î.wwɓ'L*mVWW:u*..׷Rzj#DHB>>wMz{{zk&/ٯcy͛7755F# xryuu]]]&m'/} .!kgyB/Νj׮]?+]‰ߴi=222**J$f٪FJ%wyĉR{cc~ϖ;v쐶?.* p `HI#55uRKRRRJJфy!ė_~)5ꊊ pa7mt4ܹsBޚ zk-[~իWFJO<ġCtۃIBׯZ놆~9փ(+-- 08P ]~M a ĝIB4\>k֬ƒwddSO=kWWWf$ӝ݇:~{%\___ӧ2؞={M6~[nIʦN*.\xԩSjȖ_'Z!޽{]#E?L4O$6PRmhh r`ttp9В77*!Z61c7nwtt>}zFF4As;cadZ`>kmm|'.]#gvwڕ|͑ <.i4ׯo۶-!!(;zh||W_}gmذA-(xxM6x 777Z{g͚5k,GG燅*1p)=rUB1Qr劷H QVKX QV(,L&`#>|ٳ]]]=<<Νk.lmmd2Bh44B؟ן"dV3}%zzzׯߋpw F(,QMMMJIIybkjkk_UdaÆ #F'%%t,OE)S-X@ײs-[>}ڜa[[[Ǝ+BBB|}}wܩrʶz NNN#Ed---?9%n޼{WW{$BsEaX1 0|jҥK-]\\/5d/W^Q(~~~w?'|5""ԩSB۷oK/ũT*ݕFRctE) WW_|믿6ѸdOOO''ٳg>|X?~gk ÍXVVd,YdܸqO??{lܸqo0HW>|xƌ'N[kt^{m޼y<j45kx{{{{{Yh&NNNaaaeeeo),z_^^PPp?VTT_fddxΙ3GR]v̘17nسgϘ1cz뭦۷r!Z>>Bo0***??_" =..᷿S֮]kcc#jw:---B{'++K4Wcc˗ßz͛7K4uO^WWWWW7m4ӣJhwy'&&|aYA KGsEɓ' !|||rrrz{{Zm||Mt"##Z@j꒪fAAA6lu y흲Zjj˥WRRR]pA_JjhZ~wN8!5666Ji&]o78uԺ:]B/Νj׮]3=8Z_G]J&ht.VcV+wQ.WWWK=Mf+jkjj|}}Ȣ`(,eѢE}w}֖}T!Dcccxxŋۥ9sHcǎ6N:pB]H!D__%66{NT*,XK/mtuu]z+W~+VDEEI8qBذ03 !.]۴iӤ GGG!Ĕ)SƍFB`i#44TVwtt 4"::z޽BPJ~Π i7((hǏ7(VH&t[GGDze˺]??7 !.\,#33ܹsRO>NF0\UUUWWgggj0@;wnGӟ4P+JZNJJRTҰ19ЌvvvGIoDŽ!\B_~,>>~AtLf+qm* XcǎdUVt \.ondu!u:  OTWWvkkkůz111#믿W^mzjiiiCCƍ#"";tnW{7uʕ!}^q7KΞ%vss=|LLK'NtvvB466rJ\|k1XUVt {{'O;;;8q"''gBRuV__ߣGfdd[駟6:Tfff\\\.oll,((zOΟ?AXۀ֯_׿7>>֭[ Ϝ9%oׯ_|Q)0i@o0(( ,'9;~222ƌۛ"h{6::z۶mQQQ I1W㺫0L9haa!gÎ;"##'L g͚U\\,:88L>}˖-R('xB.JsgҤI}}};ҍ3t AAA「6M:U./\ԩSaaaSNj%%%nnnQQQUUUγt`u/ )={M6~[nI/ 4VvݻۼuVFFWFF. j .,--5ĽEsEadZ~^sd|K`,zرYf@i?}Yiw׮]7o4qHOO=<<H #~VO/]doo?p# MEdQX81 CnժUUE:zh||W_}gmذ!99!rdža?l۶mKHH%˂ frKߗ&ǧ]gϞח_~H#JȴZH"-#Z9LsN]ʕ+`L2h;wܲeӧ fܽ{wJJѣGMxȣG:::L&kiiy %|IZZ?`Ȣ`(,+Ffqqq*Gh4**66vH}jK?͛srrBy%&&nݺU?Ozxx:wk8~]{^^BxG|wssqㆴܼdOOO''ٳg>|X`^x7 QYXXCuu[WWgcc6>iii|%""`.Fsԩc…'O׭[}⚚cbb냃.]z!ěoYPP_[[--BiiiE|Wk׮ Btww_x. !!AG$''ŒfX:9ȢVN0|jbʕcƌ]>>B}՝?'99yrBggOq֯_sYÇIQ__a0סCw744xzzrݱO4ȄW644lܸQaVZ%Ɏ;6ҁK~wppػwPgee%$$>̙3C{l#7k,RYXX8ҁ(oܹ}}}{tϟg//[nM>`t;wΟ?8??g\`֭[Ү]dff^xqٯ"''G!]*]2f̘WƎ[\\m۶ಲov#*ʑw< g9Ȣ- ٳgzxx̝;w׮] #, 9ȢpFeee]]]w.mgeeu:k⨬춶atڵZZZ=>yyyQQQ)))p)=,LƷtTzgy3gxyy0Y&<Þ&2d__ߝ;wW\V__/SL)S-X@aΝ[l9}"9Ȣ`(,+F{Ǐo[UU/^S(/?(\[/mHw[lw}C477/Yiه?...7dHf͚5k֬ѿ/xW ݻ};oF1co?ZLsȑyW\\Jѽ#J;>jҥKDGG$("##nݺR7| ??+::Z!Eպ_x. !!111K.=rAF#BdggWUUTVVnذAwHFFF|||SSStttBB͛75:EJb۶moްa+1xD0iii .LKKᇅ988TWWKuuu666aaaCꓖQXX(v]\\"""Oh,h鉉KJ&Lϟ?y ^5Pw^V{EGwyĉRFCM6邑\.ګtH]]]B|)m 4k&M?Z/f˖-&OMM]|ԒbYj'O,5:),YAsEaX1 ꫯΜ93gΜgϞ={688|rHHGOzŋ+سgӥ'^ʕ+o+]x䧟~ ڃjuGG;gicرtܸqަMRSSgΜig:PVTTtww~}-ZG}wmmm۷oOMM|X8 Ŋߥ#L:mڴiӦT>! ljh4{.BXBTꤤ$.\,#33ܹsLGbccӿD&&xBZ9Tn⨐gg窪:;;;UT}:::-[-mܸ|oDaұcd2٪UF:QVV4׷S.RӧO{zz={vq.][ZZ !^ZZZаqF:tnE2qDggg }!Dcc\.̻ho6++3Ĝ`lmmccccbblmmG.TTT|X8 }VZ5k֬}gϟOLLoLHHHOOOMMݿssszz 666MMMGK.]裏 ! Ξ=o>F~ JO>׿>3זIJJJZZ>>&M~dVݱcGdd rY ]ȓ`(, 'j L&jǎh(a0Aʢ#VdQ0YK1 p<-*t,GO>kDDĩSoB;99FSTҕD**66vhjK[u tqq ܿ(ɾW^yEP޽td2ّ#G͛ꫯb3fpuu}; SXXA0 `dffn۶JڰaUUU%%%%%%R/$$$!$%%EEE%&&;+1kjj V988TWWKuuu666aaařQXX7!\\\"""Wcbb냃.]z饌脄7o .LKKh}%$$̟?_`_o), `d !ZZZ6!FJfp\.jw9q(庠 6fyV;uԺ:RSS/_.$%%H! ^'O,^ߴigdddTT4QAA%k"NOi˖-]Segg kjj|}}z;p. NJQ#&88X U~Π i7((Hz+W+V::uj…###ŝ.]rww7:66u;t--裏۷J/566z.^]ڞ3g1vػi 44Tmt'O.^`(\Qp3֭[-m=njcFJ:R+V(JZRή!!!UUUuuuvvvUH!DBe˖uwwK/mܸ\jpBppy9%GGGyMi@kt^۷ {N&t0Qp<o=KΞ'NtvvB466rOOϫW644lܸQwOi.=C5ו+W b-/////!\.#m{yy:tHw]^{{{CCd8M0:t._ezX0ߪUd2ٱcF:`!%;;{/^ill|뭷[aJ222ƌۛbpxJJJZZZAA"==]PPP̙,!ķ~'?~|Ο?a޽&ޅ}VVVBBɓ'O8yfCBBRܺuѣG322֭[ODӟ233=<<et$P۶m?6k,RYXX8ҁC176o), `UUU?-((LjtgϞiӦ?>55֭[Knݺ啑{QYYԩSr… O:6uTV[\\OГ'O !Μ9jKKK/^ I=gҤI}}}z#;v숌0a\.5kVqq\___ӧ$~)@q78v){ӧ0.\XZZ: CBSaa#pW2/1`9d20|dQ䨞__Ǐ]А>Җ# ~0H3gr. =F<Ӈx~|TEYfZj=,_s_7zX2CQ6H3RR}AyX:NFdQ0YLո( 3g@0 s)JVT*G:`("];?QCÊQ cb(g+aTc(Caա0 P`u(:FX QV(Caա0 P`d2Bh4FPd2|gvuu;w]I$xO ɩ矿~  ,ܵkjkk[jkkjyyyQQQ))) ƠD{eeeegg'%%ܧDjG:o) Yh'BBB|}}wܩk\re[[[}}}7eʔ Zvܹe˖ӧOIkkWWرc(L !n޼{WW1<±bEST===ҮFQTF;K.DGGv8~Q&}B۽{G=䓮NBܾ}yyy~~~ _8׬YfRSsIKNNNaaaeee`P`ݺ:0#"" !KDDjsssLLLrrr}}}ppҥK9"p)))IIIuuuQQQB]]]B쪪 6KZQҢ[Zlppo`(N|jbʕcƌ7W^^^WWwy?#Bo_tvve2YAAUݎ---/\rEP8q;vKB>))IRyyyK߿?..QqK~iJJٳgg  碰p`bcc+**^G/C]hG}wߵ%&&n߾=55Uz1<<\wŋۥ9sH[:uj…Α؊ю˗/H>觟~zƍΠ 1((HVwtt 4WǏ7'(`tEp!!!UUUuuuvvvѱlٲnoƍ%%%R˅ usI/9::Ljggkcc#F4:Wo6:&W(`u;&ɤ8`T-/////\^SSSQQjggu! MOnl*ٿ0)?siӞ7npvvnllr˗ Zh!?޽{coop`gg'Nl޼YꐐT*n{ьu=FǦፍS?effzxxH=SSS{I&]v„ ~~~)))iiiQ)))CzǏ] X"VΝקQ󟽼ʊnݺ5}zmll233/^8y_}-[VTTu܀zJZ1e˖D]a4''GT*juhhJBlڴ/>>^iӦ!! ;0E;6k,ȱ`:::G:>~;1 "e"C Z0ڌc(!2840ZDtcŇ".wO.asϞ9=5P/krrrZm]]jA<7oBjjҥKĔ&c帎$Ieee˗/ B \lSVQ VQ VQ8gڄz^QSS?~KKȑ#W\yʕ?O˖-[pmmmӧO?371bBoHٲQ+،ƍraddd|qԩ#<ңN}7N4wm 3f8n\\o޼i>z'̛7>/_}TQ_r~… QQQ;233[[[˖-KNN6LnnnNvwf|-[A$ )pN/^(){xyy9ӵZmYYhtssEuP/!DHHȆ S\\|CѣfŊ[*iʀ*WUWWoذATJNwSϞ=k 梢#G !lfT 6#ioojBN䑱s粳F7'[B5| 233g̘1f!Ս،ʢq}_uzzzJJ#LK.o~45z[{;3lذ섄~/o}ݴ %O4zN}ܹsug`0,]ҥK^ټ~`ww0@{ tsmll $;'''00P-XEtVD{nG{-H:;;322222w [r;vL&ɓ''&&Zٺek`0uwwKd%u$IڵkWll^t'OV\$((\Fyvp5FGڻJsWTpqJoK=3u`sKNN.,,EK`{:1 _7XE7XEx( &M켕)x$''K߁%pu6/WY9 p((\Wĝ 2pu} zUzUzU.+F 8$F 8$F 8$F 8$F 8$F 8$F 8$F 8$F 8$F 8$F 8$FzLx{{wuu 5ͯ{SL9rs='I$OcxwnQ9oo_gϞo&￷kkq~w=جYn޼W $Fŵk*++%&fM6_>55XpaJJ_ Srsrrw~G&i) -8SIzJJJddG}}snذf~!666((G$IFY U>h4ӧOw͇>=Xaa9s?pͧNe$OOI1FO޵ jkkW'APs3>Ӕ'xB)|8!!aĉ$555 !&NXYYyDգho:?A`;::&LP^^^SSh?$^ h}KJJƍ4^[[9hР .XWFb`c`OWjjҥKĔ%fl偁Bk6]7nTj.\P@.;\nR977WqzƶtrSu_Ĩ///:х rdÓZYY\^ZZDգhoZp0AA0LΉ'eIڋPqy!DGGuxuuuJaaa>>>iii֕чXE7XE8A:\fmfܖJvttf///ѨN;knnJJJKBCC9Tضm$ҩNSWB\tɢ6!ĵkԅް (//onnnnn>sLuuuTTp7dwwٳg?^+4%k׮QC6۷=dqF֭[.]秞&FEpp}g̘^\\,0ꚮamfm߶3F|Xm-lvo޻N<ǶcZh4͘1C4q_~͛ryHHȆ #g.\ܑ*R٩pssS4hۢ\bQY6r,((($$$$$'x6nX__xD;wlmm?~mmmxx5k셧Dգhm:Aj˖-KNN6LN* QQQaHEEETT1??᯾ŋGe+Fmuxfmm-z?Q{4xุŋ|X5GkzjQQQuu fΜ(к:]677ٳC)%J;??}Վ5Iqqqgvww߷o:Æ NHHhhhj'N˓+$$$$''oٲ%886##cO=233,Ybbbjjj ĝүY&33W痕CBBRRRoա}d9k׾f9>>h„ 322j6ҔzV]]'hРAuuu:ݽ >>̙3Bs=J9uqƜK.)O{keee&Lʚ:ul6YYY/}}}-Z4k֬'zU>'H ??nOk׮X^&O{nel^~}pp{XX͛{Qܽ{wDDN1cFCCB~E@N`I:;;322222/dQ8VzUWWGFFjZ//E*$44'55Shmo/'(==]nS{ts666FGGYԑ$h4khYa3Bk{쉍8<}UzU.N#l6$Ή * * 3F 8$FПBCC; 8\ WDžpq\1 `!1 `!1 `!1 `!1 \Fe?oy_,nݺ?駟>|xHHʕ+^*h4Oaӧ5>[b]]]W#{L2rH__{O>qb2?X۷/333##jܹ֭{ !++˫Cw4p9sVQf <6R荇cB455KJKK C?` ""ƍJIkk[qq-oMMMBI]]Bӧ'$$'&&Θ1C9#IR```yyݻíٻw3gԅUUUnnnr>Z[½`oO;?SPޮM&SoCt * U.[ӳ]hkժUG=zU{{囲:O57Pߘ|iӦ1bڴi_}\nV;<<\x7y7vX!׮];bec={n^/lnn~F_믿 ?/>|ʔ)Ro_}UooO?T.Ϟy晑#GΜ9ɓ;m>;v\Qqm!޽{cbbDdɒRzHũ3uL&ӥK%-ڱc$>~[l?~cǎ~Q !6m+jSN0ag[9?~|޼y#G5kɓ'H=|l]gO̴r{u@,p**Tɓ'/ZH5~:IV^f4FchhhvvtJɉ'VVV8p`0$;vӳ>##ͪ4hP[[qܸqyyyfͭ,Y,IRGGׄ kjjSN---mii:th{{{EEG~~' ́ :t֭uuu۷otB: رC^CC^/--K}||ѻ֑$iڵBA#hdܸqEDDȳnq>؛-[ϝ;722D{'9R_1j=twwÇ'$$L8Ɏ,>*lA fO̴wYx* U.ezXEs%y͛:N;S<*++KKKrI7nܨ|=66v…Z$} î]$Is] Ecǎb2y'N555,dAA!vƍJ`6n:077WqIFSSS.]*$&&gwl偁Bk6-:wwwYjHZ$^~X;`H_{.\ ?L;ّd'1jMImQr0S~guc*  W2 pBr%s5#G}ʓ #ڵkr$I%Iڶm->ϗ$)''gƌҝx|-:ieI4[n]t:WQGU]]fs :κsww?vEUUUZl6{yyFĨ:srr,v9bt߾}~~~6gŋj:lM>I,i &7GĨ)99}yyyϟw#Nb^9_Yϔid}}x8VQ/pqhymє\A.pBTTLOf/^\YY?ݻ7>>^1dȐ`9tիWM-[,99d2%&&wY׷ms wss>2ӧOjeeeFM~R\|_VaÆ={[3dȐ .***, -Fds[v `lί=;wlmm?~mmmxx5kHq6@81S~Ό=_Q5jVABt:#GÇkZ< JIX'7EEEyzz_/ SRR֯_uɓ'gϞmWUWWoذa̙w7""J޶9rNee rE+.....^xcp\GUTT߿_]x q֮]{YuC YUW9jRYΩS-zq^Oׯ_뭷|||V\gرG)к:m !4'f~ )))iiiBeWFFСCfsZZRe˖ڌիW?S4NI22رcs̱"..n:Æ NHHhhhj'N˳n^+++0aBVVՈSIDATSfhzggg+dK/===555??/++Kׇyt9r5kdffZsZo6;;;&L|GAAA7nɹtym !mvיr򧡰رcNd~sng)ñ ;ٙ!IF~cIIIhhOjjjgg\l6_>88=,,LyᒰzVRJ/ۧ=333<<#,,,##CyD\Ƚ{ts666FGGY|ܳgOxx… ĝpݻ#""t:݌3WE<-**j`ՙ:]vzN7yݻwۜ;ٞ={bcc}||z}ll,p}{H썺;'''00P-X@y b [label="ReceiveNotification(M-Notification.ind)"]; ...; --- [label="Content Type is application/vnd.wap.mms-message"]; ...; b => a [label="Activate context for MMS"]; ...; b => d [label="Download(M-Retrieve.conf)"]; ...; b => d [label="Upload(M-NotifyResp.ind)"]; ...; b => a [label="Deactivate context for MMS"]; ...; b -> c [label="MessageAdded(MMS payload)"]; } nuntium-1.4+15.10.20150902/docs/assets/generate_msc.sh0000755000015300001610000000037112571563375022651 0ustar pbuserpbgroup00000000000000#!/bin/sh quit() { echo "ERROR: $1" exit 1 } for input in *.msc do output=$(echo $input | sed 's/msc$/png/') [ -n "$output" ] || quit "output file empty" mscgen -i "$input" -o "$output" -T png [ "$?" = 0 ] || quit "msgen failed to run" done nuntium-1.4+15.10.20150902/docs/assets/receiving_success_deferral_disabled.png0000644000015300001610000005742112571563375027572 0ustar pbuserpbgroup00000000000000PNG  IHDR_ժ pHYs+ IDATx}PTG d|c@%B\d AQ. )őʺRVhy-͢$&e B.""1CEB\E[F q~d~ɓsB p&.#J'\x_:tƏqwQ:xǍ7=Ĵ>SOM{駽.\7Դ%oٲeڵƍkOk׮I&YĖ_V=rȂ ~К5kfΜY[[[[[;cƌ<^v6ͫz7}ׯ[ig-^.[$44+%%_nmm}7]\\ZKQ'Nٱc5[}}Il)=3555{ ~VWW766XbڴiFJ堠֔F/J |u֙F]#EqԩI&UTTH5&L8uKl4iRnnnCCC}}}ZZ3q& @VhF4.]*դgff30Ϟ=+8~Tj+++VZJц5BY:5+/_+nذ|©p)=~7,,Lzj/]d{yIc'F޶7kƌRC1}t!! [ccctt2..yVڄKHU._qe˖%$$Xg R!11qϞ=BH)=*+++o޼i:z ?###<<^X~}ooK/dR =yaQQQG4YV]p?wr_V4{HAHP^0Xx!t #ٳDT٤߇˖-kjjJIIIOOɑfԤ~t͚5R `EDDxyyUUUyyyEFF)K s텅uuuEEE999ׯ7[i6%%77hG jhhx .]gY䮿~kX8g^^^Җ{!DccR4z@-mi<}V+ݺlosQP__wʕwyg…C &<<ǧ̙3IIIRekrrrYYYYYYRRfk?L0aժU}YYY֭[VZJfͪ_mIBk׮5|MCCâEl,{`aTfffvvC:96+00e[nk{ sLAl)Ϙ1c„ YYYR@R-L555B={XFa 2eIRRmhh r`bbp9RݻCBB|||Zʈ_1sLF#`[~x,B?%XCBB?s>˗_pa̘1g꧟~ZzmŊ7n٨q)=X]iӦ4VEPSSSjjW_}u/@-Xpss1c=ɓ>>>Zv kΙ3gΜ9-"UbtRzʕ+*ju֭˗/t 0 p)=(((BF/T^zvast:۶mSTÎRpڵ{֭[R?*JVT*C<3!u.խ[{k׮93#=Ȣ`( é?jIII<__aXZZo߾e4t^^^~~~gg=y VX1fիWkjj hZ+=<3#F':n̘1o`)|KLP=rxʼn'l`]]]N3 sњ=д-!!!]]]&͈>Hnotvvכ>?33l!!!}}}cǎqFPPPGG3ڑEdQYSP*nnn˃Ο?ܸq߿l*^;eѯʫ*m۶'J_[bݺu6tիW^ZJ"$$D*HCr"---/;wÇ-l) _~dJ'H"%%B^갢"99gf{_gƐ܉~O͌Ό%[[[ccc111` .\`۹`},_I& !Ξ=nXTJ+Pnn9lc×ə:\ ۖO^XXvZӕ8_F^YlYSSSJJJzzzNNg0rss33""˫-22RzLİl>͌iyf,1ml4۷Bؾ}{FFH ;F0eʔgBZ]WW'jhhS*G~~~O6ذGJw=Jhf̘#L+WיݬY[ !; /^Tdʕ+ űcF:5X۷Z"---##?ٳgWTT!rrrҒ3iҤ`Ke񹹹SN*zuǎه:tPNNNffT,?H:5k{qqӧ'm,l) KN8aZw޻>}}zKsss7lذqƯkqqTz!ĉ'B9s\rQ: =׷C Ϝ9sÆ RiӦ).+ (//BڵK˖$ҾH_ѨjZhf^^^zdDJKKJelllkkkTTT``-a$..L2Ez-_3cvZ̘R]v͚5KTFFF:uJ!}cz}lllII٩ֶonYAS p R8[n8qbĉ#CҥK]]]cƌXS$>,YA{Eด5f̘;vt bFê0pe=X=Pz6:=Ȣppn#pwQ222 frGߗ:1 YAtlfTi:!B>駟fo1 0c[ Yt``ͭR5k̜9vƌyyyRb޼y]]]o棏>zuMjs ;v?~\Ѹx6nlltqqx'\ngٻwo@@` I N4)77>--g1jZ]]ظbŊiӦA`%0ŸiӦUUU?~<666,,ʈF&]x1&&F޽]ٖ,//G}-[(J!V5PVVV&T*{{{4ZhhhMMMWWWJJ/oHܵ-Ùlɓ'ۖ0(8(_P8:(c4fn!D__JZ*WWWHe!DqqT3]EEErW111 6khaTܹSן:uJ-TTT[ ƐٳgǏ*ZmeeQKVy'OJ/,`%0ӟ4i<3gbbb_niDѡ~p;wܲe^uww߳g^qqqijjt`XXXAA<-oF%b_z=99YZlHHHd~jt?~\&Wέ6|gfv&$::ڰۢ"#, , 3܇K2eʔ)SFҎў . ZV!m9K3f=zTniӦٳgx)Ӆќ8^)֪-cH ##}ѢE֭;s挥jkk}ݥKN2ERV3:/ !^jF&gl"4=PTjCC… FaVTT޽;22rΝϜ9sǎCL΅RKb8wmcCw>/!,k:!&L0m#eʔ)SLye=1zIJ[ʔ)SP(F<y䑠 iҥK׬Y#]\~fppP*xxxΞ=#77Wڠj˱HJJ駟JKKSSSMHK[ ۻ⚚BBBz-6˖-jf1 zC;mt駟ZԩS{tiiir3t^111uuu QQQNo;::bccm<5+ftV҇y]Yl 1:wɷ:00-l{0^^^|@jjjIIIhhQwwԎݻw$$$TUU;`=*, , so86$#Vd?_| ƌ#hoo4RX `4#jO?-bŊ7nlTYANvڦMҤUQԔW_ݼy/(((w0*0 poAk(;ODPDDD}GroY__/dKӧر^|G6loFYA{E1 koo?qDrr/\UUO?o_>88XRkϟw ҥʫ*m۶'tŋyzzΝ;æ=!<8q͟?f4DV^zj %KTO>t\A7vRO=GheFoY ȑ# ,okz^)))nݒϨ"99yHmZ  ILLܺuٙ`4baWZZ_QQ!/)..޺ukMMM___bbBa[[ FGGڵ6444--ʹ+Vׇ9r(B!󫪪v޽{{ȇh4Ĵ7nkv_|1%%%##CiӦ۷o=_[VΎ6(wwjemmKTTԐdgg/_|…۷ovǍpBc[:͜9sr9>>^*O4R*9s&&&z@g^Ԥj7o|Iecc0!55HFZ5R$JZ/..}}}`R*XիSLjnn6ֈ[Vٰaó.]*դgff͕6zjӦMBf#=Ȣ`(;Fc_}UGGǼyN>}㏗.]xbDDlԩ?,=~}ܸqqqq{BϜ9SzBU._qe˖%$$=1::Z~g#KaaaR}XXVtr޼yRaر:~{(++g>uiʛ7o6/~wvv._|˖-YYYca؎;L҈WZZ*ʚ1cƌ3v500PQQ188(pss{try!IJe222Zmzz|gφ+6l`=B+ZG!*KZ9*""˫*6.]zWo޼) ~wv]VV6S10Qرc bʕ#(--MMM5ܚTZZT*=*5oN>mG !\RRR;XߥZGjhh0l`)ɓ'{yyIW !J-gmi.//mJbO0eeeeeeIIICjT*8PYYiؾ7 pp,bYr9sF: _9sf冕iii.]ڿKKKNNΤI...%񹹹SNBxxx>}z߾}C;222>ߟ={RKdfffgg:tСC999WҠK.}׭[?Z:_SC Ɛuی3&///--??wȑm۶eggk4=ۙb-ݾ};a088hX)mxpp0??ڴiJ2>>KjӇ˅v풻*-- T*QQQF= Ϝ9s,E߯hjZh4/$5;֭[Lj̙nÁl SL> Ӈ/Yow3i$R9gO yA{Ez,æP(zc,; VHYtъ, q)=F=ϝ;z6:=Ȣppn#pw;v.IK`+ptc_dQY{btxgJ  M222z}FFHэ-ptf7^Yw%YAc(F%=XK`(؃, caaaaaaaaaaaaaaaaaaaaaaaaQ(vp:,)F1(؃, 0 pQN ptܑA{EdQ88F81 caa;=Ȣ`(؃, (=F8vp:,qGYA{EXt(ÎQ8:6=ȢMa`?줽0]v瞻uԿJtu:J*R^zӧO[ܹsFvh9?۶mSTnz]6- (˛׭[=gΜ6z+V3fի555-kjjZޚ>cVd}@@@WWٷ|}}m:9f̘~{#Rz8:o) Y)\z颣#""֯_c!!!]]]RAAA}7쬯7*MC/^8qz"#07nuttx{{t\F1~c=#k֬㏥:nW6i>W Baz9Bqa!B/,YR?,hiiYxܹsބ~Oұə:\W-k:ٙ6|B۷o )۷gddt pctw}WZٳΝ;555>GH WFF^@[qQEv~&ŎQ V¨ƎQNQNQNQNQNQNQNQNQNQNQNQNQNGG:x1 0 GP(F:Ȣ`(؃, (=F8vp:,qGYA{EXt(ÎQNQ8:H =Ȣ`( t1 0 GIdQYAca;Fx W E{{-Cj`vlvڵ{֭[R*Jt*RT(qvr9y mT*R^zӧO[?e{ i幽us=wڵ'oQ88FdAA+ƌ#zjMMa˚Vk} 5.+;;;X^^kڤn;96t1cƤ'()|K`ȢBߵrH l7v1$$KڤP(""">#oY__o6*议SN;=m^xʼn'6޸q#(({(;FEO=ɓ].tW_zܠe~~~s=|T> .lmmBܾ}[QZZmxzJJJEEte4PEEErr׭[700`iwPACBBta ȝOz'T*<<<䚃Ο?ܸq߿<3/Jo~72RzB_.YDR'3**0aa=iӦ>ݻw޽{޽R7oFGGڵ6444--M駟fff&$$,_\188(8p@DDaQQQZ(+lٲe--- Ufɰ2))ҥK[?~͚5/ҒbŊ#Gϋ-5kVee޽{j43wg#&559111--ƍVVnt_fz-{E+ ie[[J1:JTVWWKRVy'OJR +((Gv^|YtR&===33S ϛ7opp̆jF,ʕ;wNBtw3IDATw]@#ǓZTT$IHH8{RV2fOPQ\\,UIVV8p ((0[pj当B~7,,Lz&5^j˗7nܸlٲAkkkll|xLL… OJNNy'uSN{Be#(-->}T_]]%-֯_K/I ̳yIcJ+s+0a… l9Y,G}߰F磏>jEˤ˖-j[nnn:tss39""˫-22;>}zaaڵk]%%%OFWz狊?.{py677[}zlc3 !n߾mS(;v(aba͚5ΰJ^=zT*{yy-'O%])/hllT*~~~W\)))ihhxw.\h8|P!tKiNV_|(6WW䲲$WWWw0=)F3cƌz "<<ǧ̙3IIIM2ٳ?RVuuu544\vÄ VZgmݺUjoemaen/^TCV\P(;6ҁC]~~ҥK{zzbbb\\\}]k4G}t`` ;;;33b!DNNݽ855#//Ow榤(F(aaa'NxONN^ht1\]]w!_8o%T!KssuRWWM6%$$?Ҡ>>>oj*J?9((IѬYfڵ|@jjjIIIhhty~뭷rss'NxS2B'NhΜ9۷o@[ "##'LVZZ*K*//1cƄ ĝk4ZV5Ԡ400PTƶFEE]v͚5KTFFFJ2%%%qqqrH>2e0%[$vʕB}PTH u1Q uL%Fa4-RC2:+PDfҔ2Zu8؀t(RJ 2H-c1QkEbEa%*K?d {}={9=眛$'7_$I^yAI'IBBB/^sN%22R1))s4"6Sgc+IR|||EEq'JKK_uclt/1:R?fQ}ɒ%39sf4#'ߡCӻx=aaa<᷶oQg2rrr&F?j]Ҧhb$77w@m"""6n((1Oq%(ۘ~s|?%?ÛI[kv%E@ fQp3F1q( Q(e6%I2-"ywG +F0NŊQV”ƊQCb9$FhQCb9$FhQCb9$FhQ$1b(!1 t0fQPY`ʑ91 @sX1 @sHB8`%E@ fQQ44(ԎI@ fQPY`ʑ91 @sX1 @sHB8`%E@ fQQ4P;ޫ΂ ~ܼyvjޮ1N]gq4nZmFSG;,_w/&((, J0BH궶۷wvvFDDwlP̢((, c('믿?VXbƌ+V8r\xW^y% gǏ߱cGhh%Konoo~vZU{={juFF5ĐHVuAAAAAA7om6t?~e~ꩧ>se˖~!.9qK/ꫯ_)))555FjjjL&ӘX,.K$Ebvƍ^!ٳg7ll4N8{ƍ4%%%ō=JJJWxM&SCCÇUUU)))[҈è! msssSSSڒ>|8NW^b6vzqaa}μ<4|С_L>ۻʕ+o=tU!DNNNVV֑#GΞ=3gΜP6|||򢣣y!qՐFG}G999Gf4fdd[?srrݻ7B=y}xyy秥W:|'ٓ;PI=;oiii)a׭[e{u``($${Ŷ;UVV.\`0_p!&&f…$ ̟?`0$$$tttHl=n@T]]-(++eFEea$}}}{ GMͳX,reY $i͗\ב$i߾}qqqs1 Ty`%Er:nj$I:}U|yuw0U1̢P9c `<,2P;; w=k@ fQFf6U&Yp!2P;%`%Er1!<<\rrWz`Hb*1͒$fw%P; wؾJ0̢P9VbJbg= s%PY`%Er9$FhQCb9$FhQCb9$FhQCb9:IL*VP;N` c%E@ fQQ44(ԎI@ fQPY`ʑ91 @sX1 @sHB8`%E@ fQQ44(ԎI@ fQPY`ʑ91 @sX1 c=(,F:jZV~.u:ÇioowQs^x_nF9}}}gϞ/8p`NkooW؈md_x?0N S(T(D{ncc}IccbqW<#>}G\|v 7l%?ЍXgǎEEEYYYuuuo"t啑ewDb`DEEUVVڗTWWGEE+UTT$''GFFVUU9888rܻw2%%V>NIIɾ}^}e˖X^۽{ݻ#nݺ L%%%F.Z555&ɾ·~bŊ3fXȑ#rٳgW^~O?uQ+,_r s=rO?~gΜ1LKKg.\ xrg8_YY3b겿$))x'%^?~eΝ̶uk/_>V͛6o5#3$ '(Dnhh655yxx*={699yÆ F1!!ĉ^~K9,퍍qFYYYSSSXXXZZ>HMM5---&)>>uK3gΌKLL뫩׿ffffdd455%&& !;EGY :ЍNvvvzzʕ+KKK?s!Č3V\۵k׻[SSSXX(p8---ݶqKMMTAAA}}}yyyyyy]]\ܔhrb P7 !.^n:$###33ŋ%55u۶mKׅ\hXjkkZ,zܹsrakkrll}۶mq֣x B%򑗷ovV.\4MIϟ/޲eK^~ZSS3sLyppp}$I:>*I CCC\5##IѣGCBBnj, J0BX1 0L&Smmmoo୭k֬ 2FqժU;vwӦMo~7ׯ_(s9fQ:Q'\t矿rʕ+WFѣGݻ'pӸ8!࠳rg7eիW߿/|rzzݻ#3hl<(^`};7o鉌FFFn!f͚5@<$F&PTT^ojj􌎎FQN!Diiigg5kN:dɒ_ ׯ_o6-KFFm; yzzأ|eVV֢E-ZTVV600 7hCY^\~&C7bkOCCCnZ^^>}pKqњB<~xH<$F&дiL&SUUUUUUrri_ I8Po)(,FH%g#,{aa +^^^999{uw bZ߿k׮WdvJt:wSqP9nլ۷o;;w; LӧOoܸ1<<|,d@jK(ƍO>]ZZ, J0B8cb޽:.<<-<<VzZg;&(M;Xa+=b<ŊQy44(!1 @sH4(!1 @sH4(!1 @sϪnݡIENDB`nuntium-1.4+15.10.20150902/docs/assets/send_success_delivery_disabled.msc0000644000015300001610000000073712571563375026603 0ustar pbuserpbgroup00000000000000msc { hscale = 3; a [label="ofono"], b [label="nuntium"], c [label="telepathy-ofono"], d [label="Message Center"]; c => b [label="SendMessage(message payload)"]; c << b [label="MMS ObjectPath"]; ...; b => a [label="Activate context for MMS"]; ...; b => d [label="Upload(M-Send.req)"]; b << d [label="M-Send.conf)"]; ...; b => a [label="Deactivate context for MMS"]; --- [label="M-Send.conf Response Status OK"]; b -> c [label="PropertyChanged(MMS Object Path, satus=Sent)"]; } nuntium-1.4+15.10.20150902/README.md0000644000015300001610000000101112571563375016673 0ustar pbuserpbgroup00000000000000# nuntium `nuntium` is a golang written component that is supposed to be a drop in replacement for `mmsd`, interacting between `ofono`'s push client and `telepathy-ofono`. This is not a full `mmsd` implementation and only covers most of the feature requirements that satisfy an Ubuntu Phone (aka Ubuntu Touch). ## Documentation * [Architecture](docs/architecture.md) * [Testing](docs/testing.md) Addtional information: * [mmsd documentaion](https://kernel.googlesource.com/pub/scm/network/ofono/mmsd/+/master/doc/) nuntium-1.4+15.10.20150902/test/0000755000015300001610000000000012571563531016374 5ustar pbuserpbgroup00000000000000nuntium-1.4+15.10.20150902/test/test.go0000644000015300001610000001005412571563375017710 0ustar pbuserpbgroup00000000000000package 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) } } nuntium-1.4+15.10.20150902/scripts/0000755000015300001610000000000012571563531017104 5ustar pbuserpbgroup00000000000000nuntium-1.4+15.10.20150902/scripts/testcoverage.sh0000755000015300001610000000145512571563375022151 0ustar pbuserpbgroup00000000000000#!/bin/bash # run on CI service w/something like: # # go get github.com/axw/gocov/gocov # go get github.com/mattn/goveralls # COVERALLS="-service drone.io -repotoken $COVERALLS_TOKEN" ./test-coverage.sh # echo "mode: set" > acc.out fail=0 # Standard go tooling behavior is to ignore dirs with leading underscors for dir in $(find . -maxdepth 10 -not -path './.git*' -not -path '*/_*' -type d); do if ls $dir/*.go &> /dev/null; then go test -coverprofile=profile.out $dir || fail=1 if [ -f profile.out ] then cat profile.out | grep -v "mode: set" >> acc.out rm profile.out fi fi done # Failures have incomplete results, so don't send if [ -n "$COVERALLS" ] && [ "$fail" -eq 0 ] then $HOME/gopath/bin/goveralls -v -coverprofile=acc.out $COVERALLS fi rm -f acc.out exit $fail nuntium-1.4+15.10.20150902/mms/0000755000015300001610000000000012571563531016211 5ustar pbuserpbgroup00000000000000nuntium-1.4+15.10.20150902/mms/encode_decode_test.go0000644000015300001610000000643712571563375022357 0ustar pbuserpbgroup00000000000000/* * 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 . */ 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)) } } nuntium-1.4+15.10.20150902/mms/decoder_payload_test.go0000644000015300001610000000477612571563375022741 0ustar pbuserpbgroup00000000000000/* * 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 . */ 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) TestDecodeSuccessfulMRetrieveConf(c *C) { inputBytes, err := ioutil.ReadFile("test_payloads/m-retrieve.conf_success") c.Assert(err, IsNil) mRetrieveConf := NewMRetrieveConf("55555555") dec := NewDecoder(inputBytes) err = dec.Decode(mRetrieveConf) c.Assert(err, IsNil) c.Check(mRetrieveConf.MessageId, Equals, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") c.Check(mRetrieveConf.From, Equals, "11111111111/TYPE=PLMN") c.Check(mRetrieveConf.To[0], Equals, "2222222222/TYPE=PLMN") } func (s *PayloadDecoderTestSuite) TestDecodeInvalidMSendConf(c *C) { inputBytes := []byte(`719

Disculpe,ha ocurrido un error: Failure to Query from Radius Server


Por favor, regrese al menu anterior o acceda al siguiente link.

^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() } nuntium-1.4+15.10.20150902/mms/mms.go0000644000015300001610000003212412571563375017344 0ustar pbuserpbgroup00000000000000/* * 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 . */ 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 Priority 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: 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) IsLocal() bool { return strings.HasPrefix(mNotificationInd.ContentLocation, "http://localhost:9191/mms") } 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 } nuntium-1.4+15.10.20150902/mms/test_payloads/0000755000015300001610000000000012571563531021064 5ustar pbuserpbgroup00000000000000nuntium-1.4+15.10.20150902/mms/test_payloads/m-retrieve.conf_success0000644000015300001610000000017312571563375025551 0ustar pbuserpbgroup00000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaUv)11111111111/TYPE=PLMN2222222222/TYPE=PLMN NoSubjectnuntium-1.4+15.10.20150902/mms/test_payloads/m-send.conf_success0000644000015300001610000000016312571563375024654 0ustar pbuserpbgroup00000000000000ad6babe2628710c443cdeb3ff39679ac Mensaje recibido correctamentem7wC7J5L04SBvD3307@mms.personal.com.arnuntium-1.4+15.10.20150902/mms/encoder_test.go0000644000015300001610000000721612571563375021232 0ustar pbuserpbgroup00000000000000/* * 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 . */ 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) } nuntium-1.4+15.10.20150902/mms/attachments.go0000644000015300001610000002233312571563375021064 0ustar pbuserpbgroup00000000000000/* * 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 . */ 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) ReadAttachmentParts(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.ReadAttachment(&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) ReadAttachment(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 } nuntium-1.4+15.10.20150902/mms/download.go0000644000015300001610000000474512571563375020367 0ustar pbuserpbgroup00000000000000/* * 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 . */ 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 } } } nuntium-1.4+15.10.20150902/mms/parameters.go0000644000015300001610000001330512571563375020713 0ustar pbuserpbgroup00000000000000/* * 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 . */ 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", } nuntium-1.4+15.10.20150902/mms/encoder.go0000644000015300001610000003041512571563375020170 0ustar pbuserpbgroup00000000000000/* * 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 . */ 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 doesn'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": for i := 0; i < f.Len(); i++ { err = enc.writeStringParam(TO, f.Index(i).String()) if err != nil { break } } 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)) } nuntium-1.4+15.10.20150902/mms/decoder.go0000644000015300001610000003427212571563375020163 0ustar pbuserpbgroup00000000000000/* * 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 . */ package mms import ( "fmt" "log" "reflect" ) func NewDecoder(data []byte) *MMSDecoder { return &MMSDecoder{Data: data} } type MMSDecoder struct { Data []byte Offset int log string } func (dec *MMSDecoder) setPduField(pdu *reflect.Value, name string, v interface{}, setter func(*reflect.Value, interface{})) { if name != "" { field := pdu.FieldByName(name) if field.IsValid() { setter(&field, v) dec.log = dec.log + fmt.Sprintf("Setting %s to %s\n", name, v) } else { log.Println("Field", name, "not in decoding structure") } } } func setterString(field *reflect.Value, v interface{}) { field.SetString(v.(string)) } func setterUint64(field *reflect.Value, v interface{}) { field.SetUint(v.(uint64)) } func setterSlice(field *reflect.Value, v interface{}) { field.SetBytes(v.([]byte)) } 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 = (0x7f to check for short) // Length-quote = // 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 var endOffset int origOffset := dec.Offset if dec.Data[dec.Offset+1] <= SHORT_LENGTH_MAX || dec.Data[dec.Offset+1] == LENGTH_QUOTE { if length, err := dec.ReadLength(nil); err != nil { return err } else { endOffset = int(length) + 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) } // skip the rest of the content type params if endOffset > 0 { dec.Offset = endOffset } 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 { // field in the MMS protocol toField, err := dec.ReadEncodedString(reflectedPdu, "") if err != nil { return err } // field in the golang structure to := reflectedPdu.FieldByName("To") toSlice := reflect.Append(to, reflect.ValueOf(toField)) reflectedPdu.FieldByName("To").Set(toSlice) 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]) dec.setPduField(reflectedPdu, hdr, v, setterString) 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 dec.setPduField(reflectedPdu, hdr, uint64(v), setterUint64) return v, nil } func (dec *MMSDecoder) ReadByte(reflectedPdu *reflect.Value, hdr string) (byte, error) { dec.Offset++ v := dec.Data[dec.Offset] dec.setPduField(reflectedPdu, hdr, uint64(v), setterUint64) return v, nil } func (dec *MMSDecoder) ReadBoundedBytes(reflectedPdu *reflect.Value, hdr string, end int) ([]byte, error) { v := []byte(dec.Data[dec.Offset:end]) dec.setPduField(reflectedPdu, hdr, v, setterSlice) 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) dec.setPduField(reflectedPdu, hdr, value, setterUint64) 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, "") } dec.setPduField(reflectedPdu, hdr, v, setterUint64) 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-- dec.setPduField(reflectedPdu, hdr, v, setterUint64) 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) skipFieldValue() error { switch { case dec.Data[dec.Offset+1] < LENGTH_QUOTE: l, err := dec.ReadByte(nil, "") if err != nil { return err } length := int(l) if dec.Offset+length >= len(dec.Data) { return fmt.Errorf("Bad field value length") } dec.Offset += length return nil case dec.Data[dec.Offset+1] == LENGTH_QUOTE: dec.Offset++ // TODO These tests should be done in basic read functions if dec.Offset+1 >= len(dec.Data) { return fmt.Errorf("Bad uintvar") } l, err := dec.ReadUintVar(nil, "") if err != nil { return err } length := int(l) if dec.Offset+length >= len(dec.Data) { return fmt.Errorf("Bad field value length") } dec.Offset += length return nil case dec.Data[dec.Offset+1] <= TEXT_MAX: _, err := dec.ReadString(nil, "") return err } // case dec.Data[dec.Offset + 1] > TEXT_MAX _, err := dec.ReadShortInteger(nil, "") return err } 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]) valStart := 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 _, err = dec.ReadEncodedString(&reflectedPdu, "From") if valStart+size != dec.Offset { err = fmt.Errorf("From field length is %d but expected size is %d", dec.Offset-valStart, size) } 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.ReadAttachment(&ctMember); err != nil { return err } //application/vnd.wap.multipart.related and others if ctMember.FieldByName("MediaType").String() != "text/plain" { err = dec.ReadAttachmentParts(&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: // TODO This should be ReadShortInteger instead, but we read it // as a byte because we are not properly encoding the version // either, as we are using the raw value there. To fix this we // need to change the encoder and the MMS_MESSAGE_VERSION_1_X // constants. _, err = dec.ReadByte(&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: log.Printf("Skipping unrecognized header 0x%02x", param) err = dec.skipFieldValue() } if err != nil { return err } } return nil } func (dec *MMSDecoder) GetLog() string { return dec.log } nuntium-1.4+15.10.20150902/mms/mms_test.go0000644000015300001610000000234612571563375020406 0ustar pbuserpbgroup00000000000000/* * 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 . */ package mms import . "launchpad.net/gocheck" type MMSTestSuite struct{} var _ = Suite(&MMSTestSuite{}) func (s *MMSTestSuite) TestNewMSendReq(c *C) { recipients := []string{"+11111", "+22222", "+33333"} expectedRecipients := []string{"+11111/TYPE=PLMN", "+22222/TYPE=PLMN", "+33333/TYPE=PLMN"} mSendReq := NewMSendReq(recipients, []*Attachment{}, false) c.Check(mSendReq.To, DeepEquals, expectedRecipients) c.Check(mSendReq.ContentType, Equals, "application/vnd.wap.multipart.related") c.Check(mSendReq.Type, Equals, byte(TYPE_SEND_REQ)) } nuntium-1.4+15.10.20150902/mms/decoder_test.go0000644000015300001610000000276612571563375021225 0ustar pbuserpbgroup00000000000000/* * 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 . */ package mms import ( "errors" . "launchpad.net/gocheck" ) type DecoderTestSuite struct{} var _ = Suite(&DecoderTestSuite{}) func (s *DecoderTestSuite) TestDecodeStringNoNullByteTerminator(c *C) { inputBytes := []byte{ //stub byte 0x80, // 0x3c, 0x68, 0x74, 0x6d, 0x6c, 0x3e, } expectedErr := errors.New("reached end of data while trying to read string: ") 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, // 0x3c, 0x73, 0x6d, 0x69, 0x6c, 0x3e, 0x00, } dec := NewDecoder(inputBytes) str, err := dec.ReadString(nil, "") c.Check(str, Equals, "") c.Check(err, IsNil) } nuntium-1.4+15.10.20150902/.gitignore0000644000015300001610000000017512571563375017416 0ustar pbuserpbgroup00000000000000**/*.swp cmd/nuntium/nuntium cmd/nuntium-decode-cli/nuntium-decode-cli cmd/nuntium-inject-push/nuntium-inject-push test/test nuntium-1.4+15.10.20150902/cmd/0000755000015300001610000000000012571563531016160 5ustar pbuserpbgroup00000000000000nuntium-1.4+15.10.20150902/cmd/nuntium-decode-cli/0000755000015300001610000000000012571563531021645 5ustar pbuserpbgroup00000000000000nuntium-1.4+15.10.20150902/cmd/nuntium-decode-cli/decode.go0000644000015300001610000000257212571563375023433 0ustar pbuserpbgroup00000000000000package main import ( "fmt" "io/ioutil" "os" "path/filepath" "github.com/ubuntu-phonedations/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) } } nuntium-1.4+15.10.20150902/cmd/nuntium/0000755000015300001610000000000012571563531017657 5ustar pbuserpbgroup00000000000000nuntium-1.4+15.10.20150902/cmd/nuntium/loop.go0000644000015300001610000000301712571563375021166 0ustar pbuserpbgroup00000000000000/* * 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 . */ 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) } nuntium-1.4+15.10.20150902/cmd/nuntium/main.go0000644000015300001610000000425312571563375021144 0ustar pbuserpbgroup00000000000000/* * 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 . */ package main import ( "log" "os" "syscall" "github.com/ubuntu-phonedations/nuntium/ofono" "github.com/ubuntu-phonedations/nuntium/telepathy" "launchpad.net/go-dbus/v1" ) 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() } nuntium-1.4+15.10.20150902/cmd/nuntium/mediator.go0000644000015300001610000003066312571563375022030 0ustar pbuserpbgroup00000000000000/* * 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 . */ package main import ( "fmt" "io/ioutil" "log" "os" "sync" "github.com/ubuntu-phonedations/nuntium/mms" "github.com/ubuntu-phonedations/nuntium/ofono" "github.com/ubuntu-phonedations/nuntium/storage" "github.com/ubuntu-phonedations/nuntium/telepathy" ) type Mediator struct { modem *ofono.Modem telepathyService *telepathy.MMSService NewMNotificationInd chan *mms.MNotificationInd NewMSendReq chan *mms.MSendReq 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.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 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.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() var proxy ofono.ProxyInfo var mmsContext ofono.OfonoContext if mNotificationInd.IsLocal() { log.Print("This is a local test, skipping context activation and proxy settings") } else { var err error 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 { if err := storage.UpdateDownloaded(mNotificationInd.UUID, filePath); err != nil { log.Println("When calling UpdateDownloaded: ", err) return } } mRetrieveConf, err := mediator.handleMRetrieveConf(mNotificationInd.UUID) if err != nil { log.Print(err) return } mNotifyRespInd := mRetrieveConf.NewMNotifyRespInd(useDeliveryReports) if err := storage.UpdateRetrieved(mNotifyRespInd.UUID); err != nil { log.Print("Can't update mms status: ", err) return } if !mNotificationInd.IsLocal() { // TODO deferred case filePath := mediator.handleMNotifyRespInd(mNotifyRespInd) if filePath == "" { return } mediator.sendMNotifyRespInd(filePath, &mmsContext) } else { log.Print("This is a local test, skipping m-notifyresp.ind") } } func (mediator *Mediator) handleMRetrieveConf(uuid string) (*mms.MRetrieveConf, error) { var filePath string if f, err := storage.GetMMS(uuid); err == nil { filePath = f } else { return nil, fmt.Errorf("unable to retrieve MMS: %s", err) } mmsData, err := ioutil.ReadFile(filePath) if err != nil { return nil, fmt.Errorf("issues while reading from downloaded file: %s", err) } mRetrieveConf := mms.NewMRetrieveConf(uuid) dec := mms.NewDecoder(mmsData) if err := dec.Decode(mRetrieveConf); err != nil { return nil, fmt.Errorf("unable to decode m-retrieve.conf: %s with log %s", err, dec.GetLog()) } 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") } return mRetrieveConf, nil } func (mediator *Mediator) handleMNotifyRespInd(mNotifyRespInd *mms.MNotifyRespInd) string { 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) return filePath } func (mediator *Mediator) sendMNotifyRespInd(filePath string, mmsContext *ofono.OfonoContext) { defer os.Remove(filePath) proxy, err := mmsContext.GetProxy() if err != nil { log.Println("Cannot retrieve MMS proxy setting", err) return } msc, err := mmsContext.GetMessageCenter() if err != nil { log.Println("Cannot retrieve MMSC setting", err) return } if _, err := mms.Upload(filePath, msc, proxy.Host, int32(proxy.Port)); err != nil { log.Printf("Cannot upload m-notifyresp.ind encoded file %s to message center: %s", filePath, 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 } nuntium-1.4+15.10.20150902/cmd/nuntium-inject-push/0000755000015300001610000000000012571563531022106 5ustar pbuserpbgroup00000000000000nuntium-1.4+15.10.20150902/cmd/nuntium-inject-push/server.go0000644000015300001610000000156412571563375023757 0ustar pbuserpbgroup00000000000000package main import ( "bytes" "io" "net/http" "os" "time" ) func createSpace(mRetrieveConfPath string, done chan<- bool) (handler http.HandlerFunc, err error) { if mRetrieveConfPath != "" { f, err := os.Open(mRetrieveConfPath) if err != nil { return handler, err } handler = func(w http.ResponseWriter, r *http.Request) { defer f.Close() http.ServeContent(w, r, "mms", time.Time{}, f) done <- true } } else { handler = func(w http.ResponseWriter, r *http.Request) { http.ServeContent(w, r, "mms", time.Time{}, bytes.NewReader(mRetrieveConf)) done <- true } } return handler, err } func copyMMS(mRetrieveConfPath, mmsPath string) error { src, err := os.Open(mRetrieveConfPath) if err != nil { return err } dst, err := os.Create(mmsPath) if err != nil { return err } defer dst.Close() _, err = io.Copy(dst, src) return err } nuntium-1.4+15.10.20150902/cmd/nuntium-inject-push/main.go0000644000015300001610000000246712571563375023400 0ustar pbuserpbgroup00000000000000package main import ( "fmt" "net/http" "os" flags "github.com/jessevdk/go-flags" ) func main() { var args struct { // Sender is only used in the push notification. Sender string `long:"sender" short:"s" description:"the sender of the MMS" default:"0118 999 881 99 9119 7253"` // EndPoint is the name where nuntium listens to on the System Bus. EndPoint string `long:"end-point" required:"true" description:"dbus name where the nuntium agent is listening for push requests from ofono"` // MRetrieveConf is an alternative file to use as m-retrieve.conf, no mangling is done with it. MRetrieveConf string `long:"m-retrieve-conf" description:"Use a specific m-retrieve.conf to test"` } parser := flags.NewParser(&args, flags.Default) if _, err := parser.Parse(); err != nil { os.Exit(1) } fmt.Println("Creating web server to serve mms") done := make(chan bool) mmsHandler, err := createSpace(args.MRetrieveConf, done) if err != nil { fmt.Println("Issues while creating mms local server instance:", err) os.Exit(1) } http.HandleFunc("/mms", http.HandlerFunc(mmsHandler)) go http.ListenAndServe("localhost:9191", nil) //http.FileServer(http.Dir(servedDir))) if err := push(args.EndPoint, args.Sender); err != nil { fmt.Println(err) os.Exit(1) } <-done fmt.Println("Finished serving mms") } nuntium-1.4+15.10.20150902/cmd/nuntium-inject-push/push.go0000644000015300001610000000327112571563375023425 0ustar pbuserpbgroup00000000000000package main import ( "fmt" "launchpad.net/go-dbus/v1" ) const ( pushInterface string = "org.ofono.PushNotificationAgent" pushMethod string = "ReceiveNotification" ) func push(endPoint, sender string) error { conn, err := dbus.Connect(dbus.SystemBus) if err != nil { return err } obj := conn.Object(endPoint, "/nuntium") 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, // h t t p : / / 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, // l o c a l h o s t 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, // : 9 1 9 1 / m m s 0x3a, 0x39, 0x31, 0x39, 0x31, 0x2f, 0x6d, 0x6d, 0x73, 0x00, } info := map[string]*dbus.Variant{"LocalSentTime": &dbus.Variant{"2014-02-05T08:29:55-0300"}, "Sender": &dbus.Variant{sender}} reply, err := obj.Call(pushInterface, pushMethod, data, info) if err != nil || reply.Type == dbus.TypeError { return fmt.Errorf("notification error: %s", err) } return nil } nuntium-1.4+15.10.20150902/cmd/nuntium-inject-push/payload.go0000644000015300001610000005077412571563375024111 0ustar pbuserpbgroup00000000000000package main var mRetrieveConf = []byte{ // Type m-Retrieve.conf 0x8c, 0x84, // Transaction Id "123456789\0" 0x98, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x00, // Version 1.3 0x8d, 0x93, // Message Id "abdefghij\0" 0x8b, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71, 0x72, 0x72, 0x73, 0x74, 0x00, // Date 0x85, 0x04, 0x54, 0x5a, 0xc0, 0x37, // From size + token address present + "01189998819991197253" + 0x89, 0x20, 0x80, 0x30, 0x31, 0x31, 0x38, 0x39, 0x39, 0x39, 0x38, 0x38, 0x31, 0x39, 0x39, 0x39, 0x31, 0x31, 0x39, 0x37, 0x32, 0x35, 0x33, // "/TYPE=PLMN" 0x2f, 0x54, 0x59, 0x50, 0x45, 0x3d, 0x50, 0x4c, 0x4d, 0x4e, 0x00, // Content Type application/vnd.wap.multipart.related 0x84, 0x1b, // Attachments 0xb3, 0x89, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x73, 0x6d, 0x69, 0x6c, 0x00, 0x8a, 0x3c, 0x73, 0x6d, 0x69, 0x6c, 0x3e, 0x00, 0x03, 0x2f, 0x83, 0x2b, 0x1b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x73, 0x6d, 0x69, 0x6c, 0x00, 0x85, 0x73, 0x6d, 0x69, 0x6c, 0x2e, 0x78, 0x6d, 0x6c, 0x00, 0x8e, 0x73, 0x6d, 0x69, 0x6c, 0x2e, 0x78, 0x6d, 0x6c, 0x00, 0xc0, 0x22, 0x3c, 0x73, 0x6d, 0x69, 0x6c, 0x3e, 0x00, 0x3c, 0x73, 0x6d, 0x69, 0x6c, 0x3e, 0x20, 0x20, 0x20, 0x3c, 0x68, 0x65, 0x61, 0x64, 0x3e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x3e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x30, 0x30, 0x25, 0x22, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3d, 0x22, 0x31, 0x30, 0x30, 0x25, 0x22, 0x20, 0x66, 0x69, 0x74, 0x3d, 0x22, 0x6d, 0x65, 0x65, 0x74, 0x22, 0x20, 0x2f, 0x3e, 0x3c, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x54, 0x65, 0x78, 0x74, 0x22, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x30, 0x30, 0x25, 0x22, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3d, 0x22, 0x31, 0x30, 0x30, 0x25, 0x22, 0x20, 0x66, 0x69, 0x74, 0x3d, 0x22, 0x73, 0x63, 0x72, 0x6f, 0x6c, 0x6c, 0x22, 0x20, 0x2f, 0x3e, 0x3c, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x30, 0x30, 0x25, 0x22, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3d, 0x22, 0x31, 0x30, 0x30, 0x25, 0x22, 0x20, 0x66, 0x69, 0x74, 0x3d, 0x22, 0x6d, 0x65, 0x65, 0x74, 0x22, 0x20, 0x2f, 0x3e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x3e, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x3e, 0x20, 0x20, 0x20, 0x3c, 0x62, 0x6f, 0x64, 0x79, 0x3e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x70, 0x61, 0x72, 0x20, 0x64, 0x75, 0x72, 0x3d, 0x22, 0x35, 0x30, 0x30, 0x30, 0x6d, 0x73, 0x22, 0x3e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x69, 0x6d, 0x67, 0x20, 0x73, 0x72, 0x63, 0x3d, 0x22, 0x63, 0x69, 0x64, 0x3a, 0x75, 0x62, 0x75, 0x6e, 0x74, 0x75, 0x2e, 0x6a, 0x70, 0x67, 0x22, 0x20, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x20, 0x2f, 0x3e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x70, 0x61, 0x72, 0x3e, 0x3c, 0x70, 0x61, 0x72, 0x20, 0x64, 0x75, 0x72, 0x3d, 0x22, 0x33, 0x73, 0x22, 0x3e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x74, 0x65, 0x78, 0x74, 0x20, 0x73, 0x72, 0x63, 0x3d, 0x22, 0x63, 0x69, 0x64, 0x3a, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x30, 0x2e, 0x74, 0x78, 0x74, 0x22, 0x20, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x54, 0x65, 0x78, 0x74, 0x22, 0x20, 0x2f, 0x3e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x70, 0x61, 0x72, 0x3e, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x62, 0x6f, 0x64, 0x79, 0x3e, 0x20, 0x3c, 0x2f, 0x73, 0x6d, 0x69, 0x6c, 0x3e, 0x27, 0x95, 0x3a, 0x0d, 0x9e, 0x85, 0x75, 0x62, 0x75, 0x6e, 0x74, 0x75, 0x2e, 0x6a, 0x70, 0x67, 0x00, 0x8e, 0x75, 0x62, 0x75, 0x6e, 0x74, 0x75, 0x2e, 0x6a, 0x70, 0x67, 0x00, 0xc0, 0x22, 0x75, 0x62, 0x75, 0x6e, 0x74, 0x75, 0x2e, 0x6a, 0x70, 0x67, 0x00, 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x02, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x00, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05, 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e, 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13, 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x03, 0x03, 0x03, 0x04, 0x03, 0x04, 0x08, 0x04, 0x04, 0x08, 0x10, 0x0b, 0x09, 0x0b, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x40, 0x00, 0x40, 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x00, 0x1b, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x07, 0x06, 0x09, 0x00, 0x02, 0x04, 0x05, 0xff, 0xc4, 0x00, 0x32, 0x10, 0x00, 0x01, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02, 0x05, 0x04, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x11, 0x00, 0x07, 0x12, 0x08, 0x13, 0x21, 0x31, 0x41, 0x51, 0x62, 0x71, 0x14, 0x22, 0x61, 0x72, 0x91, 0x33, 0x42, 0x82, 0xa1, 0x15, 0x17, 0x23, 0x32, 0xff, 0xc4, 0x00, 0x19, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x07, 0x05, 0x01, 0x02, 0x04, 0xff, 0xc4, 0x00, 0x2b, 0x11, 0x00, 0x01, 0x02, 0x04, 0x05, 0x03, 0x05, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x11, 0x03, 0x05, 0x06, 0x21, 0x00, 0x04, 0x12, 0x31, 0x51, 0x13, 0x41, 0x81, 0x22, 0x32, 0x61, 0x71, 0x91, 0x23, 0xa1, 0xb1, 0x52, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xe5, 0x56, 0xb6, 0x5b, 0x73, 0xb5, 0x57, 0x5e, 0xe5, 0xcf, 0x31, 0xe8, 0x51, 0x52, 0xdc, 0x46, 0x54, 0x04, 0x99, 0xaf, 0x65, 0x2c, 0xb1, 0x9f, 0x93, 0xea, 0xa5, 0x63, 0xd1, 0x29, 0xc9, 0xfb, 0x0f, 0x3a, 0xf3, 0x6a, 0xb6, 0xe2, 0xa1, 0xb9, 0x97, 0x5b, 0x34, 0x38, 0xcb, 0x2c, 0x45, 0x6c, 0x77, 0xe6, 0xc9, 0xc6, 0x7b, 0x2c, 0x02, 0x01, 0x23, 0xea, 0x39, 0xc2, 0x47, 0xc9, 0xf8, 0x07, 0x4e, 0x6a, 0x0d, 0x06, 0x91, 0x6c, 0x52, 0x23, 0x50, 0xa8, 0x50, 0x51, 0x12, 0x0c, 0x44, 0xf0, 0x69, 0xa4, 0x7b, 0x7c, 0x92, 0x7d, 0x54, 0xa2, 0x7c, 0x95, 0x1f, 0x24, 0xe8, 0xad, 0x47, 0x51, 0x89, 0x40, 0x10, 0x60, 0x5e, 0x29, 0xfc, 0x48, 0xe4, 0xfc, 0xf0, 0x3c, 0x9f, 0x9a, 0x15, 0x13, 0x44, 0xaa, 0xa3, 0x51, 0xcd, 0x66, 0xc9, 0x4c, 0x04, 0x96, 0xb6, 0xea, 0x3c, 0x0e, 0x00, 0xee, 0x7c, 0x0e, 0xe4, 0x4d, 0xec, 0xde, 0x9a, 0xf6, 0xe6, 0xd9, 0x65, 0xb7, 0x2a, 0xd0, 0x95, 0x5f, 0x9c, 0x00, 0xe6, 0xec, 0xdf, 0x0c, 0xf2, 0xf7, 0xe2, 0xc8, 0x38, 0x03, 0xf7, 0x15, 0x6a, 0x9f, 0x06, 0x99, 0x4d, 0xa6, 0x35, 0xd8, 0xa6, 0x53, 0xa2, 0xc4, 0x6c, 0x78, 0xe1, 0x1d, 0x94, 0xb6, 0x3f, 0x84, 0x81, 0xaf, 0xa7, 0xd7, 0xc7, 0xcf, 0x8d, 0x55, 0x2d, 0xae, 0x97, 0xb7, 0xca, 0xe9, 0x8a, 0x89, 0xd0, 0xec, 0x67, 0xa1, 0xc7, 0x70, 0x05, 0x21, 0x75, 0x27, 0xdb, 0x8a, 0x54, 0x0f, 0xbf, 0x05, 0x9e, 0x7f, 0xca, 0x75, 0x35, 0x5c, 0x59, 0x94, 0xe5, 0x65, 0xca, 0xa2, 0x1e, 0x03, 0x90, 0x3c, 0x0b, 0x0c, 0x5c, 0xa1, 0xc0, 0x92, 0x53, 0x30, 0x83, 0x08, 0x70, 0x53, 0xd8, 0x96, 0x04, 0xf9, 0x37, 0x27, 0xc9, 0xc4, 0x8a, 0x75, 0x32, 0x9b, 0x53, 0x6b, 0xb1, 0x53, 0xa7, 0x45, 0x96, 0xd9, 0xfe, 0xc9, 0x0c, 0x25, 0xc1, 0xfc, 0x28, 0x1d, 0x4c, 0x6f, 0x0e, 0x9a, 0xf6, 0xde, 0xe6, 0x6d, 0xc7, 0x29, 0x70, 0x57, 0x40, 0x9a, 0x47, 0xe5, 0x76, 0x09, 0xff, 0x00, 0xcb, 0x3e, 0xdc, 0x9a, 0x51, 0xc6, 0x3f, 0x69, 0x4e, 0x97, 0x8d, 0xf4, 0xcb, 0x7e, 0xda, 0xd7, 0x6d, 0xb1, 0x13, 0x73, 0x29, 0x4d, 0xd3, 0xed, 0xfa, 0xcd, 0x66, 0x35, 0x31, 0xf9, 0xd1, 0xa7, 0x34, 0xea, 0x53, 0xdc, 0x51, 0xc2, 0x32, 0x0e, 0x50, 0x55, 0x82, 0x90, 0x48, 0xc6, 0x4f, 0xce, 0x34, 0x9b, 0xdf, 0x0d, 0x81, 0xd9, 0xc8, 0x7b, 0x41, 0x5c, 0x99, 0x4f, 0xb5, 0x69, 0x54, 0x29, 0x34, 0x3a, 0x73, 0xb2, 0xe1, 0xcd, 0x8c, 0xd8, 0x6d, 0xd4, 0x38, 0xda, 0x72, 0x94, 0xad, 0x7e, 0xae, 0x05, 0x10, 0x12, 0x42, 0xb2, 0x4f, 0x2f, 0x9c, 0x1d, 0x6c, 0xca, 0xa5, 0x73, 0x68, 0x48, 0x89, 0x1a, 0x0a, 0xcc, 0x23, 0x0f, 0xb1, 0x70, 0xf6, 0x7d, 0xb6, 0x6f, 0xbb, 0x60, 0xc5, 0x43, 0x50, 0xd3, 0x91, 0xe2, 0xc0, 0xca, 0xe6, 0x61, 0x08, 0xe2, 0x35, 0xb5, 0x24, 0x03, 0xa6, 0xed, 0xee, 0x17, 0x77, 0xec, 0x2e, 0xde, 0x1f, 0x81, 0x5b, 0x91, 0xb5, 0x17, 0x56, 0xda, 0x54, 0x13, 0x1e, 0xb7, 0x1d, 0x2e, 0xc4, 0x7c, 0x91, 0x1a, 0x73, 0x19, 0x2c, 0xbd, 0x8f, 0x6c, 0x9f, 0x29, 0x57, 0xca, 0x4f, 0x9f, 0xb8, 0xf3, 0xac, 0x66, 0xba, 0x31, 0x5e, 0xa0, 0xd2, 0x2e, 0x7a, 0x44, 0x9a, 0x15, 0x7a, 0x02, 0x25, 0xc1, 0x96, 0x8e, 0x2e, 0xb4, 0xbf, 0xf4, 0x41, 0xfe, 0xd5, 0x03, 0xe4, 0x28, 0x79, 0x07, 0x41, 0xad, 0xd6, 0xdb, 0x79, 0xfb, 0x65, 0x74, 0xbb, 0x44, 0x90, 0xb5, 0x3f, 0x11, 0xd1, 0xdf, 0x83, 0x24, 0x8c, 0x77, 0x99, 0x27, 0x03, 0x3f, 0x0a, 0x07, 0xc2, 0x87, 0xc8, 0xf8, 0x23, 0x4c, 0x69, 0xca, 0x8c, 0x4d, 0x87, 0x42, 0x3d, 0xa2, 0x8f, 0xc5, 0x0e, 0x47, 0xcf, 0x23, 0xc8, 0xf8, 0x9a, 0x56, 0xd4, 0x4a, 0xa9, 0xd5, 0x0c, 0xde, 0x50, 0x95, 0x40, 0x51, 0x6b, 0xee, 0x93, 0xc1, 0xe4, 0x1e, 0xc7, 0xc1, 0xbb, 0x12, 0x9e, 0xe9, 0xb2, 0xcc, 0x66, 0xd7, 0xdb, 0x98, 0xd5, 0x47, 0x63, 0x84, 0xcf, 0xaf, 0x1f, 0xc6, 0xbc, 0xb2, 0x3f, 0x37, 0x6b, 0x24, 0x32, 0x9f, 0xb7, 0x1c, 0xab, 0xfc, 0xf5, 0x58, 0x42, 0x16, 0xe2, 0xd2, 0xdb, 0x68, 0x52, 0xd6, 0xb2, 0x12, 0x94, 0xa4, 0x65, 0x4a, 0x24, 0xe0, 0x00, 0x3d, 0xc9, 0x3e, 0x35, 0xf7, 0xed, 0xcd, 0x89, 0x51, 0xba, 0x2b, 0x14, 0x0d, 0xbd, 0xb7, 0x10, 0xd9, 0x95, 0x2b, 0xb3, 0x02, 0x39, 0x59, 0xc2, 0x10, 0x12, 0x8c, 0x15, 0xa8, 0x8f, 0x44, 0xa5, 0x29, 0x2a, 0x38, 0x19, 0xf1, 0xf3, 0xa4, 0xfd, 0x4f, 0xa5, 0xd3, 0xb1, 0x0e, 0xd1, 0xf7, 0x76, 0x45, 0x79, 0x37, 0x45, 0x3a, 0xd8, 0x98, 0xcd, 0x42, 0xb1, 0x4f, 0x31, 0x3f, 0x0e, 0xa0, 0xca, 0x54, 0x32, 0xeb, 0x47, 0x92, 0xb9, 0x76, 0xc9, 0x0b, 0xe0, 0xac, 0x64, 0x23, 0xd7, 0xdb, 0x40, 0x15, 0x94, 0xcd, 0xcf, 0x33, 0x11, 0x73, 0xc9, 0x1e, 0x8d, 0x57, 0x3c, 0x0f, 0xf4, 0xb0, 0xe3, 0xb6, 0x2c, 0x29, 0x99, 0x4b, 0xa9, 0x3c, 0xa6, 0x5e, 0x54, 0xb5, 0x0e, 0xae, 0x8f, 0x4a, 0x7f, 0xe9, 0x5f, 0x7b, 0x0d, 0x4a, 0x7b, 0x92, 0x2e, 0x71, 0xb2, 0xdb, 0xae, 0x92, 0xec, 0x9b, 0x7f, 0x6a, 0x6a, 0x8a, 0xdc, 0xb4, 0xc7, 0x15, 0xca, 0xcd, 0x35, 0x6a, 0x99, 0x35, 0xe5, 0x00, 0x9a, 0x32, 0x31, 0xc8, 0x06, 0x89, 0xf0, 0x95, 0x20, 0x80, 0x56, 0xbf, 0x72, 0x08, 0xcf, 0x1f, 0x07, 0x07, 0x6d, 0x75, 0xb3, 0x54, 0xb3, 0xac, 0xda, 0x7d, 0xab, 0x26, 0xda, 0x6e, 0xe3, 0xab, 0x52, 0x92, 0xa8, 0x6b, 0xaa, 0xaa, 0x69, 0x69, 0x89, 0x4d, 0x36, 0xa2, 0x96, 0x9d, 0x03, 0x81, 0x59, 0x2a, 0x40, 0x4e, 0x73, 0x8f, 0x9f, 0x7d, 0x6b, 0xfa, 0xe6, 0xdc, 0x09, 0x70, 0xad, 0x3b, 0x7e, 0xca, 0xa4, 0xcb, 0x52, 0x63, 0x5c, 0x4a, 0x72, 0x6c, 0xb5, 0x20, 0xe3, 0xbb, 0x1d, 0xae, 0x1c, 0x10, 0x7e, 0x95, 0x2d, 0x61, 0x44, 0x7d, 0x03, 0x42, 0xbd, 0x6c, 0x4e, 0x66, 0x42, 0x49, 0x98, 0x19, 0x49, 0x60, 0xd0, 0x50, 0x96, 0x51, 0xb1, 0x77, 0x63, 0x77, 0xb5, 0xb7, 0x7f, 0x93, 0x83, 0x14, 0xbc, 0x8c, 0xd5, 0x59, 0x25, 0x4c, 0x67, 0xe7, 0xa8, 0x22, 0x2c, 0xa9, 0x29, 0x72, 0x02, 0x59, 0xd2, 0x59, 0x88, 0x20, 0x1d, 0xb4, 0x8b, 0x7a, 0x41, 0x2e, 0x70, 0xbf, 0x85, 0xd6, 0x16, 0xde, 0x6e, 0x6d, 0x3d, 0xfb, 0x27, 0x78, 0xec, 0x45, 0xc0, 0xa4, 0xd4, 0x80, 0x6d, 0xd9, 0x31, 0xe4, 0xaa, 0x43, 0x4d, 0x9c, 0x82, 0x95, 0xa8, 0x04, 0xa5, 0xc6, 0xca, 0x48, 0x0a, 0x0b, 0x46, 0x4a, 0x48, 0xce, 0xb5, 0xdb, 0x97, 0xd3, 0xad, 0x62, 0xf4, 0xdb, 0x69, 0x08, 0xb5, 0xb7, 0x8a, 0xeb, 0xb8, 0x9b, 0x43, 0x28, 0x99, 0x49, 0x83, 0x51, 0xa8, 0x34, 0xfc, 0x39, 0x29, 0x48, 0xe4, 0x94, 0x15, 0xa1, 0x01, 0x4e, 0x12, 0x3f, 0xf8, 0x5a, 0x94, 0x70, 0x70, 0x7f, 0x5d, 0x04, 0xb4, 0xbd, 0xe8, 0xd7, 0x7a, 0xa8, 0x94, 0x3b, 0x5a, 0xaf, 0x65, 0x5f, 0x57, 0x5d, 0x3e, 0x99, 0x16, 0x98, 0xf2, 0x24, 0x52, 0x97, 0x3e, 0x5a, 0x5a, 0x1d, 0xb7, 0x79, 0x77, 0x1a, 0x41, 0x51, 0x19, 0x09, 0x5a, 0x79, 0x60, 0x7a, 0x77, 0x0e, 0xbb, 0x28, 0x9c, 0x26, 0x6d, 0x14, 0xe4, 0xe6, 0x8c, 0x75, 0x02, 0x02, 0xbd, 0xa7, 0xe8, 0xb3, 0x02, 0x38, 0x7d, 0x8f, 0xde, 0x3c, 0xd4, 0xb4, 0xca, 0xa9, 0xc8, 0x02, 0x65, 0x20, 0x74, 0xe8, 0x50, 0x26, 0x1f, 0xbc, 0x6e, 0x03, 0xa5, 0xdc, 0x83, 0xb0, 0x2c, 0x6e, 0x38, 0x6c, 0x10, 0xdc, 0x43, 0x8d, 0x2d, 0x4d, 0x3a, 0x85, 0x21, 0x68, 0x51, 0x4a, 0xd2, 0xa1, 0x85, 0x25, 0x40, 0xe0, 0x82, 0x3d, 0x88, 0x3e, 0x08, 0xd4, 0xa7, 0xa9, 0x1b, 0x35, 0xab, 0xa3, 0x6d, 0xe5, 0x54, 0x9a, 0x64, 0x2a, 0x6d, 0x04, 0xfe, 0x39, 0x95, 0x01, 0xf9, 0xbb, 0x7e, 0x03, 0xa9, 0xfb, 0x71, 0xfc, 0xdf, 0xe1, 0xa4, 0xdf, 0x50, 0xe8, 0xb5, 0xce, 0xf0, 0xdc, 0x53, 0x6c, 0xea, 0xa4, 0x2a, 0x85, 0x2a, 0xa2, 0xf2, 0x27, 0xb6, 0xf4, 0x37, 0x52, 0xe3, 0x5d, 0xc7, 0x50, 0x14, 0xea, 0x41, 0x4f, 0x8f, 0x0e, 0x73, 0x24, 0x7e, 0xba, 0x98, 0xd4, 0x21, 0x33, 0x52, 0xa7, 0xca, 0xa6, 0xc9, 0x19, 0x6a, 0x5b, 0x0e, 0x30, 0xe0, 0xfa, 0x56, 0x92, 0x93, 0xfe, 0x8e, 0x8c, 0xc1, 0x88, 0xa9, 0x4c, 0xc0, 0x29, 0x25, 0xfa, 0x6a, 0xdc, 0x77, 0x00, 0xb1, 0xfd, 0x18, 0x79, 0x99, 0x82, 0x8a, 0x8a, 0x4c, 0x61, 0xc4, 0x4b, 0x75, 0x91, 0xb1, 0xdc, 0x12, 0x1c, 0x79, 0x49, 0xfe, 0xc6, 0x3f, 0x67, 0x6e, 0x2f, 0xba, 0x8d, 0xad, 0x59, 0xa0, 0x6e, 0x15, 0xb8, 0xa6, 0xff, 0x00, 0x15, 0x17, 0xb3, 0x3d, 0x80, 0xe0, 0xca, 0x16, 0x95, 0xa3, 0x25, 0x0a, 0xc1, 0xcf, 0x15, 0x25, 0x45, 0x27, 0x07, 0x3e, 0x74, 0x9f, 0xaa, 0x75, 0x47, 0xff, 0x00, 0x7c, 0x39, 0x48, 0xda, 0x17, 0x68, 0x29, 0xb5, 0xa9, 0xf7, 0x44, 0xc6, 0x69, 0xf5, 0x7a, 0x82, 0xe5, 0xfe, 0x21, 0x5d, 0x95, 0x28, 0x72, 0x69, 0xa1, 0xc5, 0x3c, 0x7b, 0x84, 0x04, 0x73, 0x56, 0x70, 0x17, 0xe9, 0xef, 0xae, 0x7d, 0x74, 0xdb, 0x78, 0xb5, 0x73, 0xed, 0xbc, 0x5a, 0x6b, 0x8f, 0x05, 0x4d, 0xa0, 0xab, 0xf0, 0x2f, 0x24, 0x9f, 0xcd, 0xdb, 0xf2, 0x5a, 0x57, 0xdb, 0x8e, 0x53, 0xfe, 0x1a, 0xab, 0x21, 0x6b, 0x69, 0x69, 0x75, 0xa7, 0x14, 0x85, 0xa1, 0x41, 0x49, 0x52, 0x4e, 0x14, 0x95, 0x03, 0x90, 0x41, 0xf6, 0x20, 0xf9, 0xd7, 0xda, 0xac, 0xe6, 0x6e, 0x47, 0x1e, 0x2e, 0x45, 0x27, 0xd1, 0xaa, 0xe3, 0x91, 0xfe, 0x87, 0x4f, 0x1d, 0xb1, 0x98, 0x99, 0x64, 0xba, 0xac, 0xca, 0x65, 0xe6, 0xab, 0x4f, 0xf2, 0xe8, 0x1a, 0x55, 0x7f, 0x4a, 0xbe, 0xb6, 0x3a, 0x55, 0xd8, 0x82, 0x1c, 0x61, 0xa5, 0xd7, 0x3d, 0x81, 0x2e, 0x6d, 0xa9, 0x6f, 0x5e, 0xb4, 0x98, 0x8a, 0x54, 0x6b, 0x75, 0x4e, 0x41, 0x96, 0x94, 0x02, 0x7b, 0x51, 0xdd, 0xe1, 0xc1, 0x67, 0xe1, 0x29, 0x5a, 0x02, 0x49, 0xfa, 0xc6, 0x85, 0x9a, 0x71, 0xed, 0xdf, 0x56, 0x76, 0x45, 0xc5, 0xb5, 0x15, 0x44, 0xee, 0x62, 0xa3, 0x7f, 0xcd, 0x51, 0xe9, 0xab, 0x4c, 0xe8, 0x2f, 0x24, 0x71, 0xac, 0x23, 0x1c, 0x41, 0x69, 0x27, 0xc2, 0x8a, 0xc9, 0x01, 0x68, 0xf6, 0x24, 0x9c, 0x71, 0xf2, 0x27, 0xf6, 0xdf, 0x44, 0xd5, 0x6b, 0xc6, 0xcd, 0xa7, 0x5d, 0x6f, 0x5c, 0xad, 0x5b, 0x95, 0x5a, 0xaa, 0x55, 0x35, 0x74, 0x95, 0xc2, 0x2e, 0xb1, 0x15, 0xa7, 0x14, 0x54, 0xd3, 0x40, 0xf3, 0x0b, 0x04, 0x20, 0x8c, 0xe7, 0x3f, 0x1e, 0xc7, 0x5b, 0x13, 0xa9, 0x68, 0x9d, 0x66, 0x06, 0x6e, 0x58, 0x75, 0x95, 0xa5, 0xc8, 0xb0, 0x66, 0x61, 0x77, 0xb5, 0xf6, 0x6d, 0xec, 0x70, 0x66, 0x97, 0x9e, 0x1a, 0x5b, 0x24, 0xa9, 0x74, 0xf8, 0x74, 0xc4, 0x35, 0x94, 0xa5, 0x4c, 0x48, 0x53, 0xba, 0x8b, 0x30, 0x24, 0x81, 0xbe, 0xad, 0x99, 0x40, 0x16, 0xc1, 0x73, 0x4b, 0xbe, 0x8d, 0xb6, 0x52, 0x85, 0x5f, 0xb5, 0xea, 0xf7, 0xad, 0xf7, 0x69, 0xc0, 0xa9, 0xc5, 0xa8, 0xbc, 0x88, 0xf4, 0xa4, 0xcf, 0x8a, 0x97, 0x47, 0x6d, 0xae, 0x5d, 0xc7, 0x50, 0x14, 0x3c, 0x05, 0x2d, 0x5c, 0x72, 0x3d, 0x7b, 0x67, 0x5e, 0xf0, 0x7a, 0x3b, 0xdb, 0xfd, 0xb6, 0xa7, 0xbf, 0x7a, 0xef, 0x25, 0xfa, 0xa9, 0xb4, 0x9a, 0x68, 0x0e, 0xbd, 0x1a, 0x34, 0x75, 0x47, 0x69, 0xc3, 0x90, 0x12, 0x85, 0x2b, 0x92, 0x9c, 0x5f, 0x22, 0x40, 0x08, 0x46, 0x0a, 0x89, 0xc6, 0xb5, 0xfb, 0x93, 0xd4, 0x65, 0x4a, 0xca, 0xdb, 0x67, 0xd5, 0x6a, 0x6c, 0xfd, 0xd9, 0x6f, 0x36, 0xa6, 0x51, 0x0a, 0x91, 0x36, 0xa3, 0x4d, 0x6e, 0x3c, 0x38, 0xe9, 0x50, 0xe2, 0x95, 0x94, 0xa5, 0x65, 0x4d, 0x94, 0x8c, 0x14, 0x21, 0x49, 0x4e, 0x4e, 0x07, 0xce, 0xbb, 0x28, 0x93, 0xa6, 0x53, 0x14, 0xe6, 0xe6, 0x8c, 0x34, 0x82, 0x42, 0x7d, 0xc7, 0x87, 0x2c, 0xe0, 0x0d, 0xd9, 0xfb, 0xfd, 0x63, 0xcd, 0x49, 0x53, 0x2e, 0xa2, 0x80, 0x99, 0x6d, 0x3e, 0xea, 0xd6, 0xa0, 0x0c, 0x4f, 0x60, 0xdc, 0x16, 0x49, 0x2c, 0x49, 0xd8, 0x96, 0x16, 0x1c, 0xbe, 0x0b, 0x1d, 0x43, 0x1b, 0x59, 0xbd, 0xe1, 0xb8, 0xa0, 0x59, 0x94, 0xa8, 0x54, 0xfa, 0x55, 0x39, 0xe4, 0x40, 0x6d, 0x98, 0x6d, 0x25, 0xb6, 0xbb, 0x8d, 0x20, 0x25, 0xd5, 0x00, 0x9f, 0x19, 0x2e, 0x73, 0x04, 0xfe, 0x9a, 0x98, 0xd4, 0x26, 0xb3, 0x4d, 0xa7, 0xca, 0xa9, 0x48, 0x38, 0x6a, 0x23, 0x0e, 0x3e, 0xe1, 0xfa, 0x50, 0x92, 0xa3, 0xfe, 0x86, 0xbe, 0x97, 0x1c, 0x71, 0xd7, 0x14, 0xeb, 0xce, 0x29, 0xc7, 0x1c, 0x51, 0x5a, 0xd6, 0xb3, 0x95, 0x29, 0x44, 0xe4, 0x92, 0x7d, 0xc9, 0x3e, 0x4e, 0xa5, 0x5d, 0x48, 0x5e, 0x4c, 0xda, 0xdb, 0x6f, 0x2e, 0x9c, 0x87, 0x82, 0x66, 0xd7, 0x8f, 0xe0, 0x58, 0x48, 0x3e, 0x7b, 0x7e, 0x0b, 0xaa, 0xfb, 0x04, 0xfe, 0x5f, 0xba, 0xc6, 0x8c, 0xc1, 0x86, 0xa9, 0xb4, 0xc0, 0x21, 0x01, 0xba, 0x8a, 0xd8, 0x76, 0x04, 0xb9, 0xfc, 0x18, 0x77, 0x99, 0x8e, 0x8a, 0x76, 0x4c, 0x62, 0x44, 0x53, 0xf4, 0x61, 0xee, 0x7b, 0x90, 0x18, 0x79, 0x51, 0xfe, 0xce, 0x0b, 0xdb, 0x53, 0xb9, 0x15, 0x0d, 0xb3, 0xba, 0x9a, 0xad, 0xc7, 0x42, 0x9f, 0x88, 0xea, 0x7b, 0x13, 0xa3, 0x03, 0x8e, 0xf3, 0x24, 0x82, 0x71, 0xf0, 0xa0, 0x7c, 0xa4, 0xfc, 0x8f, 0x82, 0x74, 0xe5, 0xa0, 0x57, 0xa9, 0x17, 0x3d, 0x22, 0x35, 0x7a, 0x85, 0x39, 0x12, 0xe0, 0xcb, 0x47, 0x36, 0x9d, 0x47, 0xbf, 0xc8, 0x23, 0xd5, 0x2a, 0x07, 0xc1, 0x49, 0xf2, 0x0e, 0xb9, 0xcf, 0xad, 0x9e, 0xdb, 0xee, 0xb5, 0xd3, 0xb6, 0x53, 0xd5, 0x22, 0x89, 0x21, 0x2e, 0xc4, 0x7c, 0x83, 0x26, 0x0b, 0xe4, 0x96, 0x5e, 0xfd, 0x70, 0x3c, 0xa5, 0x5f, 0x0a, 0x1e, 0x7e, 0xe3, 0xc6, 0xa9, 0x95, 0x1d, 0x38, 0x26, 0xc3, 0xaf, 0x02, 0xd1, 0x47, 0xe2, 0x87, 0x07, 0xe7, 0x83, 0xe0, 0xfc, 0x43, 0x28, 0x9a, 0xd9, 0x54, 0xea, 0x8e, 0x53, 0x36, 0x0a, 0xa0, 0x28, 0xbd, 0xb7, 0x49, 0xe4, 0x72, 0x0f, 0x71, 0xe4, 0x5d, 0xc1, 0x7a, 0xfe, 0xbf, 0x1e, 0x75, 0x57, 0xb6, 0xba, 0xa5, 0xdf, 0x2b, 0x5e, 0x2a, 0x20, 0xc5, 0xbd, 0x9c, 0x9d, 0x1d, 0xb0, 0x02, 0x51, 0x52, 0x8e, 0xdc, 0xa2, 0x00, 0xf6, 0xe6, 0xa1, 0xcf, 0xf9, 0x51, 0xd1, 0x4e, 0xcd, 0xea, 0x47, 0x6d, 0xee, 0x86, 0x9b, 0x6a, 0xa5, 0x3d, 0x54, 0x19, 0xaa, 0x00, 0x29, 0x99, 0xdf, 0xd2, 0xe5, 0xf4, 0xbc, 0x07, 0x12, 0x3f, 0x77, 0x1d, 0x53, 0x21, 0x54, 0x69, 0xd5, 0x36, 0x44, 0x8a, 0x6d, 0x42, 0x34, 0xb6, 0x88, 0xc8, 0x5b, 0x0f, 0x25, 0xc4, 0xff, 0x00, 0x29, 0x27, 0x53, 0x55, 0x42, 0x98, 0xc9, 0x96, 0x5c, 0x2a, 0x19, 0xf8, 0x70, 0xfe, 0x45, 0x8e, 0x2e, 0x48, 0x8d, 0x25, 0xa9, 0xa1, 0x06, 0x30, 0xe3, 0x27, 0xb0, 0x2c, 0x48, 0xf0, 0x6e, 0x3f, 0x06, 0x2d, 0xe8, 0xea, 0x6e, 0xfb, 0xba, 0xee, 0xcb, 0x62, 0x4e, 0xe6, 0xd4, 0x99, 0x9f, 0x6f, 0x51, 0xeb, 0x51, 0x6a, 0x72, 0x20, 0xc5, 0x82, 0xdb, 0x69, 0x57, 0x6d, 0x47, 0x0b, 0xc0, 0x1c, 0x96, 0x53, 0x92, 0xa0, 0x92, 0x71, 0x91, 0xf3, 0x8d, 0x26, 0xf7, 0xc7, 0x7e, 0xb6, 0x72, 0x5e, 0xd0, 0x57, 0x61, 0xc1, 0xba, 0xe9, 0x35, 0xd7, 0xeb, 0x94, 0xe7, 0x62, 0x43, 0x85, 0x19, 0xd0, 0xe3, 0xab, 0x71, 0xc4, 0xe1, 0x2a, 0x5a, 0x3d, 0x5b, 0x09, 0x24, 0x28, 0x95, 0x63, 0x1c, 0x7e, 0x70, 0x35, 0xcf, 0x59, 0xd5, 0x1a, 0x75, 0x31, 0x93, 0x22, 0xa5, 0x50, 0x8d, 0x11, 0xa1, 0xe4, 0xad, 0xf7, 0x92, 0xda, 0x7f, 0x95, 0x11, 0xa9, 0x9d, 0xe5, 0xd4, 0x8e, 0xdb, 0xda, 0xec, 0xba, 0x8a, 0x6d, 0x44, 0xd7, 0xa7, 0x27, 0xc2, 0x59, 0x83, 0xfd, 0x3c, 0xfd, 0x4f, 0x11, 0xc7, 0x1f, 0xb7, 0x91, 0xd6, 0xc4, 0xaa, 0x6b, 0x36, 0x8a, 0x88, 0x90, 0x60, 0xa0, 0xc5, 0x31, 0x2c, 0xe5, 0xcb, 0x59, 0xb7, 0xd9, 0xbe, 0xed, 0x82, 0xf5, 0x0d, 0x3b, 0x4e, 0xc0, 0x8b, 0x03, 0x35, 0x99, 0x8a, 0x20, 0x26, 0x0d, 0xc2, 0x52, 0x42, 0x75, 0x5d, 0xf6, 0x17, 0x77, 0xee, 0x03, 0x91, 0xe3, 0x14, 0x5a, 0xf5, 0x7a, 0x91, 0x6c, 0x52, 0x24, 0xd7, 0x6b, 0xd3, 0x91, 0x12, 0x0c, 0x44, 0x73, 0x75, 0xe5, 0xff, 0x00, 0xa0, 0x07, 0xaa, 0x94, 0x4f, 0x80, 0x91, 0xe4, 0x9d, 0x06, 0xf7, 0x63, 0x71, 0xe7, 0x6e, 0x6d, 0xd4, 0xed, 0x6d, 0xf4, 0x2d, 0x88, 0x6c, 0xa7, 0xb1, 0x06, 0x31, 0x39, 0xec, 0xb2, 0x09, 0x23, 0x3f, 0x52, 0x8e, 0x54, 0xa3, 0xf2, 0x71, 0xe8, 0x06, 0xbc, 0xdc, 0x7d, 0xd8, 0xba, 0xb7, 0x32, 0x72, 0x5e, 0xad, 0xc8, 0x4b, 0x50, 0xd9, 0x51, 0x31, 0xa0, 0xb1, 0x90, 0xcb, 0x3f, 0xae, 0x3d, 0x54, 0xaf, 0xa9, 0x59, 0x3f, 0x18, 0x1e, 0x35, 0x8b, 0xd3, 0x1a, 0x72, 0x9c, 0x12, 0x91, 0xd7, 0x8f, 0x78, 0xa7, 0xf1, 0x23, 0x81, 0xf3, 0xc9, 0xf0, 0x3e, 0x66, 0xb5, 0xb5, 0x6c, 0xaa, 0x89, 0x43, 0x29, 0x94, 0x05, 0x30, 0x12, 0x5e, 0xfb, 0xa8, 0xf2, 0x78, 0x03, 0xb0, 0xf2, 0x6e, 0xc0, 0x7f, 0xff, 0xd9, 0x27, 0x04, 0x0d, 0x83, 0x85, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x30, 0x2e, 0x74, 0x78, 0x74, 0x00, 0x8e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x30, 0x2e, 0x74, 0x78, 0x74, 0x00, 0xc0, 0x22, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x30, 0x2e, 0x74, 0x78, 0x74, 0x00, 0x54, 0x65, 0x73, 0x74, } nuntium-1.4+15.10.20150902/ofono/0000755000015300001610000000000012571563531016535 5ustar pbuserpbgroup00000000000000nuntium-1.4+15.10.20150902/ofono/push_decode_test.go0000644000015300001610000002470612571563375022424 0ustar pbuserpbgroup00000000000000/* * 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 . */ package ofono import ( "errors" "testing" "github.com/ubuntu-phonedations/nuntium/mms" . "launchpad.net/gocheck" ) 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")) } func (s *PushDecodeTestSuite) TestDecodeTMobileUSA(c *C) { inputBytes := []byte{ 0xc0, 0x06, 0x28, 0x1f, 0x22, 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, 0x81, 0x84, 0x8d, 0x80, 0xaf, 0x84, 0x8c, 0x82, 0x98, 0x6d, 0x61, 0x76, 0x6f, 0x64, 0x69, 0x2d, 0x37, 0x2d, 0x38, 0x39, 0x2d, 0x31, 0x63, 0x30, 0x2d, 0x37, 0x2d, 0x63, 0x61, 0x2d, 0x35, 0x30, 0x66, 0x39, 0x33, 0x38, 0x34, 0x33, 0x2d, 0x37, 0x2d, 0x31, 0x33, 0x62, 0x2d, 0x32, 0x65, 0x62, 0x2d, 0x31, 0x2d, 0x63, 0x61, 0x2d, 0x33, 0x36, 0x31, 0x65, 0x33, 0x31, 0x35, 0x00, 0x8d, 0x92, 0x89, 0x1a, 0x80, 0x18, 0x83, 0x2b, 0x31, 0x39, 0x31, 0x39, 0x39, 0x30, 0x33, 0x33, 0x34, 0x38, 0x38, 0x2f, 0x54, 0x59, 0x50, 0x45, 0x3d, 0x50, 0x4c, 0x4d, 0x4e, 0x00, 0x8a, 0x80, 0x8e, 0x03, 0x0f, 0x21, 0x9f, 0x88, 0x05, 0x81, 0x03, 0x03, 0xf4, 0x80, 0x83, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x61, 0x74, 0x6c, 0x32, 0x6d, 0x6f, 0x73, 0x67, 0x65, 0x74, 0x2e, 0x6d, 0x73, 0x67, 0x2e, 0x65, 0x6e, 0x67, 0x2e, 0x74, 0x2d, 0x6d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x6d, 0x73, 0x2f, 0x77, 0x61, 0x70, 0x65, 0x6e, 0x63, 0x3f, 0x54, 0x3d, 0x6d, 0x61, 0x76, 0x6f, 0x64, 0x69, 0x2d, 0x37, 0x2d, 0x31, 0x33, 0x62, 0x2d, 0x32, 0x65, 0x62, 0x2d, 0x31, 0x2d, 0x63, 0x61, 0x2d, 0x33, 0x36, 0x31, 0x65, 0x33, 0x31, 0x35, 0x00, } dec := NewDecoder(inputBytes) c.Assert(dec.Decode(s.pdu), IsNil) c.Check(int(s.pdu.HeaderLength), Equals, 40) 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, 183) } func (s *PushDecodeTestSuite) TestDecodePlayPoland(c *C) { inputBytes := []byte{ 0x2e, 0x06, 0x22, 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, 0x8c, 0x82, 0x98, 0x31, 0x34, 0x34, 0x32, 0x34, 0x30, 0x31, 0x33, 0x31, 0x38, 0x40, 0x6d, 0x6d, 0x73, 0x32, 0x00, 0x8d, 0x92, 0x89, 0x18, 0x80, 0x2b, 0x34, 0x38, 0x38, 0x38, 0x32, 0x30, 0x34, 0x30, 0x32, 0x32, 0x35, 0x2f, 0x54, 0x59, 0x50, 0x45, 0x3d, 0x50, 0x4c, 0x4d, 0x4e, 0x00, 0x8f, 0x81, 0x86, 0x80, 0x8a, 0x80, 0x8e, 0x03, 0x03, 0xad, 0x21, 0x88, 0x05, 0x81, 0x03, 0x03, 0xf4, 0x80, 0x83, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6d, 0x6d, 0x73, 0x63, 0x2e, 0x70, 0x6c, 0x61, 0x79, 0x2e, 0x70, 0x6c, 0x2f, 0x3f, 0x69, 0x64, 0x3d, 0x31, 0x34, 0x34, 0x32, 0x34, 0x30, 0x31, 0x33, 0x31, 0x38, 0x42, 0x00, } dec := NewDecoder(inputBytes) c.Assert(dec.Decode(s.pdu), IsNil) c.Check(int(s.pdu.HeaderLength), Equals, 34) 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, 102) } nuntium-1.4+15.10.20150902/ofono/modem.go0000644000015300001610000003511112571563375020174 0ustar pbuserpbgroup00000000000000/* * 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 . */ package ofono import ( "errors" "fmt" "log" "reflect" "time" "launchpad.net/go-dbus/v1" ) const ( contextTypeInternet = "internet" contextTypeMMS = "mms" ) const ( ofonoAttachInProgressError = "org.ofono.Error.AttachInProgress" ofonoInProgressError = "org.ofono.Error.InProgress" ofonoNotAttachedError = "org.ofono.Error.NotAttached" ofonoFailedError = "org.ofono.Error.Failed" ) 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 } const PROP_SETTINGS = "Settings" const SETTINGS_PROXY = "Proxy" const SETTINGS_PROXYPORT = "ProxyPort" const DBUS_CALL_GET_PROPERTIES = "GetProperties" 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) { nextState := false 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 { nextState = true break } } if modem.pushInterfaceAvailable != nextState { modem.pushInterfaceAvailable = nextState 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 { // ofonoFailedError might be due to network issues or to wrong APN configuration. // Retrying would not make sense for the latter, but we cannot distinguish // and any possible delay retrying might cause would happen only the first time // (provided we end up finding the right APN on the list so we save it as // preferred). if dbusErr, ok := err.(*dbus.Error); ok { return dbusErr.Name == ofonoInProgressError || dbusErr.Name == ofonoAttachInProgressError || dbusErr.Name == ofonoNotAttachedError || dbusErr.Name == ofonoFailedError } return false } func (context *OfonoContext) getContextProperties(conn *dbus.Connection) { ctxObj := conn.Object(OFONO_SENDER, context.ObjectPath) if reply, err := ctxObj.Call(CONNECTION_CONTEXT_INTERFACE, DBUS_CALL_GET_PROPERTIES); err == nil { if err := reply.Args(&context.Properties); err != nil { log.Println("Cannot retrieve properties for", context.ObjectPath, err) } } else { log.Println("Cannot get properties for", context.ObjectPath, err) } } 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(2 * time.Second) } } else { // If it works we set it as preferred in ofono, provided it is not // a combined context. // TODO get rid of nuntium's internal preferred setting if !context.isPreferred() && context.isTypeMMS() { obj.Call(CONNECTION_CONTEXT_INTERFACE, "SetProperty", "Preferred", dbus.Variant{true}) } // Refresh context properties context.getContextProperties(conn) 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) isPreferred() bool { return reflect.ValueOf(oContext.Properties["Preferred"].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) settingsProxy() string { v, ok := oContext.Properties[PROP_SETTINGS] if !ok { return "" } settings, ok := v.Value.(map[interface{}]interface{}) if !ok { return "" } proxy_v, ok := settings[SETTINGS_PROXY] if !ok { return "" } proxy, ok := proxy_v.(*dbus.Variant).Value.(string) if !ok { return "" } return proxy } func (oContext OfonoContext) settingsProxyPort() uint64 { v, ok := oContext.Properties[PROP_SETTINGS] if !ok { return 80 } settings, ok := v.Value.(map[interface{}]interface{}) if !ok { return 80 } port_v, ok := settings[SETTINGS_PROXYPORT] if !ok { return 80 } port, ok := port_v.(*dbus.Variant).Value.(uint16) if !ok { return 80 } return uint64(port) } 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.settingsProxy() // we need to support empty proxies if proxy == "" { log.Println("No proxy in ofono settings") return proxyInfo, nil } proxyInfo.Host = proxy proxyInfo.Port = oContext.settingsProxyPort() 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: //- if current type=internet context, check for MessageProxy & MessageCenter; // if they exist and aren't empty AND the context is active, add it to the list //- if current type=mms, add it to the list //- if ofono's ConnectionManager.Preferred property is set, use only that context //- prioritize active and recently successfully used contexts // //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.isPreferred() { mmsContexts = []OfonoContext{context} break } else 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, DBUS_CALL_GET_PROPERTIES); 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() { if modem.identity != "" { modem.IdentityRemoved <- modem.identity } modem.modemSignal.Cancel() modem.modemSignal.C = nil modem.simSignal.Cancel() modem.simSignal.C = nil modem.endWatch <- true } nuntium-1.4+15.10.20150902/ofono/wsp_params.go0000644000015300001610000001156712571563375021260 0ustar pbuserpbgroup00000000000000/* * 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 . */ 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 ) nuntium-1.4+15.10.20150902/ofono/pushagent.go0000644000015300001610000001117212571563375021072 0ustar pbuserpbgroup00000000000000/* * 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 . */ package ofono import ( "encoding/hex" "fmt" "log" "sync" "github.com/ubuntu-phonedations/nuntium/mms" "launchpad.net/go-dbus/v1" ) /* 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) } } nuntium-1.4+15.10.20150902/ofono/context_test.go0000644000015300001610000002673712571563375021634 0ustar pbuserpbgroup00000000000000/* * 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 . */ package ofono import ( "errors" "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, preferred bool) PropertiesType { p := make(PropertiesType) p["Name"] = dbus.Variant{name} p["Type"] = dbus.Variant{cType} p["Active"] = dbus.Variant{active} p["Preferred"] = dbus.Variant{preferred} 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, 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) TestMMSOverInactiveInternet(c *C) { context1 := OfonoContext{ ObjectPath: "/ril_0/context1", Properties: makeGenericContextProperty("Context1", contextTypeInternet, false, true, true, false), } 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, 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, false), } s.contexts = append(s.contexts, context1) context2 := OfonoContext{ ObjectPath: "/ril_0/context2", Properties: makeGenericContextProperty("Context2", contextTypeMMS, false, true, 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) TestMMSOverMMSNoProxy(c *C) { context1 := OfonoContext{ ObjectPath: "/ril_0/context1", Properties: makeGenericContextProperty("Context1", contextTypeInternet, true, false, false, false), } s.contexts = append(s.contexts, context1) context2 := OfonoContext{ ObjectPath: "/ril_0/context2", Properties: makeGenericContextProperty("Context2", contextTypeMMS, false, true, false, 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, false), } s.contexts = append(s.contexts, context1) context2 := OfonoContext{ ObjectPath: "/ril_0/context2", Properties: makeGenericContextProperty("Context2", contextTypeMMS, false, true, false, 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, false), } s.contexts = append(s.contexts, context1) context2 := OfonoContext{ ObjectPath: "/ril_0/context2", Properties: makeGenericContextProperty("Context2", contextTypeMMS, false, true, false, false), } s.contexts = append(s.contexts, context2) context3 := OfonoContext{ ObjectPath: "/ril_0/context3", Properties: makeGenericContextProperty("Context3", contextTypeMMS, false, true, false, 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, false), } s.contexts = append(s.contexts, context1) context2 := OfonoContext{ ObjectPath: "/ril_0/context2", Properties: makeGenericContextProperty("Context2", contextTypeMMS, false, true, false, false), } s.contexts = append(s.contexts, context2) context3 := OfonoContext{ ObjectPath: "/ril_0/context3", Properties: makeGenericContextProperty("Context3", contextTypeMMS, false, true, false, 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, false), } s.contexts = append(s.contexts, context0) context1 := OfonoContext{ ObjectPath: "/ril_0/context1", Properties: makeGenericContextProperty("Context1", contextTypeMMS, false, true, false, false), } s.contexts = append(s.contexts, context1) context2 := OfonoContext{ ObjectPath: "/ril_0/context2", Properties: makeGenericContextProperty("Context2", contextTypeMMS, false, true, false, false), } s.contexts = append(s.contexts, context2) context3 := OfonoContext{ ObjectPath: "/ril_0/context3", Properties: makeGenericContextProperty("Context3", contextTypeMMS, true, true, false, 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, false), } s.contexts = append(s.contexts, context0) context1 := OfonoContext{ ObjectPath: "/ril_0/context1", Properties: makeGenericContextProperty("Context1", contextTypeMMS, false, true, false, false), } s.contexts = append(s.contexts, context1) context2 := OfonoContext{ ObjectPath: "/ril_0/context2", Properties: makeGenericContextProperty("Context2", contextTypeMMS, false, true, false, false), } s.contexts = append(s.contexts, context2) context3 := OfonoContext{ ObjectPath: "/ril_0/context3", Properties: makeGenericContextProperty("Context3", contextTypeMMS, false, true, false, 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) TestOnePreferredContext(c *C) { context0 := OfonoContext{ ObjectPath: "/ril_0/context0", Properties: makeGenericContextProperty("Context0", contextTypeInternet, true, true, false, false), } s.contexts = append(s.contexts, context0) context1 := OfonoContext{ ObjectPath: "/ril_0/context1", Properties: makeGenericContextProperty("Context1", contextTypeMMS, false, true, false, true), } s.contexts = append(s.contexts, context1) context2 := OfonoContext{ ObjectPath: "/ril_0/context2", Properties: makeGenericContextProperty("Context2", contextTypeMMS, false, true, false, false), } s.contexts = append(s.contexts, context2) context3 := OfonoContext{ ObjectPath: "/ril_0/context3", Properties: makeGenericContextProperty("Context3", contextTypeMMS, true, true, false, false), } s.contexts = append(s.contexts, context3) contexts, err := s.modem.GetMMSContexts("") c.Assert(err, IsNil) c.Assert(len(contexts), Equals, 1) c.Check(contexts[0], DeepEquals, context1) } func (s *ContextTestSuite) TestGetProxy(c *C) { context := OfonoContext{ ObjectPath: "/ril_0/context1", Properties: makeGenericContextProperty("Context1", contextTypeInternet, true, true, true, false), } m := make(map[interface{}]interface{}) pr := dbus.Variant{proxy.Host} pr_pt := dbus.Variant{uint16(proxy.Port)} m["Proxy"] = &pr m["ProxyPort"] = &pr_pt context.Properties["Settings"] = dbus.Variant{m} 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, false), } p, err := context.GetProxy() c.Assert(err, IsNil) c.Check(p, DeepEquals, ProxyInfo{}) } func (s *ContextTestSuite) TestGetProxyNoPort(c *C) { context := OfonoContext{ ObjectPath: "/ril_0/context1", Properties: makeGenericContextProperty("Context1", contextTypeInternet, true, true, true, false), } m := make(map[interface{}]interface{}) pr := dbus.Variant{proxy.Host} m["Proxy"] = &pr context.Properties["Settings"] = dbus.Variant{m} p, err := context.GetProxy() c.Assert(err, IsNil) c.Check(p, DeepEquals, ProxyInfo{Host: proxy.Host, Port: 80}) } nuntium-1.4+15.10.20150902/ofono/manager.go0000644000015300001610000000601212571563375020503 0ustar pbuserpbgroup00000000000000/* * 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 . */ 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 { //Use a different connection for the modem signals to avoid go-dbus blocking issues conn, err := dbus.Connect(dbus.SystemBus) if err != nil { return err; } modemAddedSignal, err := connectToSignal(conn, "/", OFONO_MANAGER_INTERFACE, "ModemAdded") if err != nil { return err } modemRemovedSignal, err := connectToSignal(conn, "/", OFONO_MANAGER_INTERFACE, "ModemRemoved") if err != nil { return err } go mm.watchModems(modemAddedSignal, modemRemovedSignal) //Check for existing modems modemPaths, err := getModems(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) } } nuntium-1.4+15.10.20150902/ofono/common.go0000644000015300001610000000443012571563375020363 0ustar pbuserpbgroup00000000000000/* * 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 . */ 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 } nuntium-1.4+15.10.20150902/ofono/push.go0000644000015300001610000000666612571563375020067 0ustar pbuserpbgroup00000000000000/* * 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 . */ package ofono import ( "errors" "fmt" "reflect" "github.com/ubuntu-phonedations/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 }