[
  {
    "path": ".gitignore",
    "content": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n\n# Architecture specific extensions/prefixes\n*.[568vq]\n[568vq].out\n\n*.cgo1.go\n*.cgo2.c\n_cgo_defun.c\n_cgo_gotypes.go\n_cgo_export.*\n\n_testmain.go\n\n*.exe\n*.test\n\n# Example keys\nexample/*.key\nexample/*.crt\n\n# Ginkgo files\n*.coverprofile\ncoverage.html\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: go\ngo:\n  - 1.3\nservices:\n  - redis-server\nbefore_script:\n  - go get github.com/onsi/ginkgo\n  - go get github.com/onsi/gomega\n  - go get code.google.com/p/go.tools/cmd/cover\n  - go install github.com/onsi/ginkgo/ginkgo\nscript: ginkgo -r --skipMeasurements --cover --trace\nenv:\n    global:\n        - PATH=$HOME/gopath/bin:$PATH\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014 timehop\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# apns\n\n[![GoDoc](https://godoc.org/github.com/timehop/apns?status.svg)](https://godoc.org/github.com/timehop/apns)\n[![Build Status](https://travis-ci.org/timehop/apns.svg?branch=master)](https://travis-ci.org/timehop/apns)\n\nA Go package to interface with the Apple Push Notification Service\n\n## Features\n\nThis library implements a few features that we couldn't find in any one library elsewhere:\n\n* **Long Lived Clients** - Apple's documentation say that you should hold [a persistent connection open](https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW6) and not create new connections for every payload\n* **Use of New Protocol** - Apple came out with v2 of their API with support for variable length payloads. This library uses that protocol.\n* **Robust Send Guarantees** - APNS has asynchronous feedback on whether a push sent. That means that if you send pushes after a bad send, those pushes will be lost forever. Our library records the last N pushes, detects errors, and is able to resend the pushes that could have been lost. [More reading](http://redth.codes/the-problem-with-apples-push-notification-ser/)\n\n## API Compatibility\n\nThe apns package may undergo breaking changes. A tool like [godep](https://github.com/tools/godep) is recommended to vendor the current release.\n\n## Install\n\n```\ngo get github.com/timehop/apns\n```\n\nCheckout the `develop` branch for the current work in progress.\n\n## Usage\n\n### Sending a push notification (basic)\n\n```go\nc, _ := apns.NewClient(apns.ProductionGateway, apnsCert, apnsKey)\n\np := apns.NewPayload()\np.APS.Alert.Body = \"I am a push notification!\"\np.APS.Badge.Set(5)\np.APS.Sound = \"turn_down_for_what.aiff\"\n\nm := apns.NewNotification()\nm.Payload = p\nm.DeviceToken = \"A_DEVICE_TOKEN\"\nm.Priority = apns.PriorityImmediate\n\nc.Send(m)\n```\n\n### Sending a push notification with error handling\n\n```go\n    c, err := apns.NewClientWithFiles(apns.ProductionGateway, \"cert.pem\", \"key.pem\")\n    if err != nil {\n        log.Fatal(\"could not create new client\", err.Error())\n    }\n\n    go func() {\n        for f := range c.FailedNotifs {\n            fmt.Println(\"Notif\", f.Notif.ID, \"failed with\", f.Err.Error())\n        }\n    }()\n\n    p := apns.NewPayload()\n    p.APS.Alert.Body = \"I am a push notification!\"\n    p.APS.Badge.Set(5)\n    p.APS.Sound = \"turn_down_for_what.aiff\"\n    p.APS.ContentAvailable = 1\n\n    p.SetCustomValue(\"link\", \"zombo://dot/com\")\n    p.SetCustomValue(\"game\", map[string]int{\"score\": 234})\n\n    m := apns.NewNotification()\n    m.Payload = p\n    m.DeviceToken = \"A_DEVICE_TOKEN\"\n    m.Priority = apns.PriorityImmediate\n    m.Identifier = 12312       // Integer for APNS\n    m.ID = \"user_id:timestamp\" // ID not sent to Apple – to identify error notifications\n\n    c.Send(m)\n```\n\n### Retrieving feedback\n\n```go\nf, err := apns.NewFeedback(s.Address(), DummyCert, DummyKey)\nif err != nil {\n\tlog.Fatal(\"Could not create feedback\", err.Error())\n}\n\nfor ft := range f.Receive() {\n\tfmt.Println(\"Feedback for token:\", ft.DeviceToken)\n}\n```\n\nNote that the channel returned from `Receive` will close after the\n[feedback service](https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW3)\nhas no more data to send.\n\n## Running the tests\n\nWe use [Ginkgo](https://onsi.github.io/ginkgo) for our testing framework and\n[Gomega](http://onsi.github.io/gomega/) for our matchers. To run the tests:\n\n```\ngo get github.com/onsi/ginkgo/ginkgo\ngo get github.com/onsi/gomega\nginkgo -randomizeAllSpecs\n```\n\n## Contributing\n\n- Fork the repo ([Recommended process](https://splice.com/blog/contributing-open-source-git-repositories-go/))\n- Make your changes\n- [Run the tests](https://github.com/timehop/apns#running-the-tests)\n- Submit a pull request\n\n## License\n\n[MIT License](https://github.com/timehop/apns/blob/master/LICENSE)\n"
  },
  {
    "path": "apns_suite_test.go",
    "content": "package apns_test\n\nimport (\n\t. \"github.com/onsi/ginkgo\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"testing\"\n)\n\nfunc TestApns(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Apns Suite\")\n}\n"
  },
  {
    "path": "badge_number.go",
    "content": "package apns\n\nimport \"encoding/json\"\n\n// BadgeNumber is much a NullInt64 in\n// database/sql except instead of using\n// the nullable value for driver.Value\n// encoding and decoding, this is specifically\n// meant for JSON encoding and decoding\ntype BadgeNumber struct {\n\tNumber uint\n\tIsSet  bool\n}\n\n// Unset will reset the BadgeNumber to\n// both 0 and invalid\nfunc (b *BadgeNumber) Unset() {\n\tb.Number = 0\n\tb.IsSet = false\n}\n\n// Set will set the BadgeNumber value to\n// the number passed in, assuming it's >= 0.\n// If so, the BadgeNumber will also be marked valid\nfunc (b *BadgeNumber) Set(number uint) {\n\tb.Number = number\n\tb.IsSet = true\n}\n\n// MarshalJSON will marshall the numerical value of\n// BadgeNumber\nfunc (b BadgeNumber) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(b.Number)\n}\n\n// UnmarshalJSON will take any non-nil value and\n// set BadgeNumber's numeric value to it. It assumes\n// that if the unmarshaller gets here, there is a\n// number to unmarshal and it's valid\nfunc (b *BadgeNumber) UnmarshalJSON(data []byte) error {\n\terr := json.Unmarshal(data, &b.Number)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Since the point of this type is to\n\t// allow proper inclusion of 0 for int\n\t// types while respecting omitempty,\n\t// assume that set==true if there is\n\t// a value to unmarshal\n\tb.IsSet = true\n\treturn nil\n}\n"
  },
  {
    "path": "badge_number_test.go",
    "content": "package apns_test\n\nimport (\n\t\"encoding/json\"\n\n\t. \"github.com/onsi/ginkgo\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/timehop/apns\"\n)\n\nvar _ = Describe(\"BadgeNumber\", func() {\n\tDescribe(\"Defaults\", func() {\n\t\tIt(\"Should have the proper default values\", func() {\n\t\t\tb := apns.BadgeNumber{}\n\t\t\tExpect(b.Number).To(Equal(uint(0)))\n\t\t\tExpect(b.IsSet).To(BeFalse())\n\t\t})\n\t})\n\n\tDescribe(\"NewBadgeNumber\", func() {\n\t\tvar b apns.BadgeNumber\n\n\t\tContext(\"with an argument\", func() {\n\t\t\tIt(\"should have values set properly\", func() {\n\t\t\t\tb.Set(5)\n\t\t\t\tExpect(b.IsSet).To(BeTrue())\n\t\t\t\tExpect(b.Number).To(Equal(uint(5)))\n\t\t\t})\n\t\t})\n\t\tContext(\"when unset\", func() {\n\t\t\tIt(\"should reset its values\", func() {\n\t\t\t\tb.Unset()\n\t\t\t\tExpect(b.IsSet).To(BeFalse())\n\t\t\t\tExpect(b.Number).To(Equal(uint(0)))\n\t\t\t})\n\t\t})\n\t})\n\n\tDescribe(\"JSON handling\", func() {\n\t\ttype BadgeNumbers struct {\n\t\t\tA apns.BadgeNumber `json:\"a\"`\n\t\t\tB apns.BadgeNumber `json:\"b\"`\n\t\t}\n\n\t\tContext(\"when marshalling\", func() {\n\t\t\tIt(\"should not error\", func() {\n\t\t\t\tbn := apns.BadgeNumber{}\n\t\t\t\tbn.Set(10)\n\t\t\t\t_, err := json.Marshal(BadgeNumbers{\n\t\t\t\t\tA: bn,\n\t\t\t\t})\n\t\t\t\tExpect(err).To(BeNil())\n\t\t\t})\n\t\t\tIt(\"should create the proper values\", func() {\n\t\t\t\tbn := apns.BadgeNumber{}\n\t\t\t\tbn.Set(10)\n\t\t\t\tbns := BadgeNumbers{\n\t\t\t\t\tA: bn,\n\t\t\t\t}\n\t\t\t\tb, _ := json.Marshal(bns)\n\t\t\t\texpected := \"{\\\"a\\\":10,\\\"b\\\":0}\"\n\t\t\t\tExpect(string(b)).To(Equal(expected))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"when unmarshalled\", func() {\n\t\t\tvar bnumbers BadgeNumbers\n\n\t\t\tIt(\"should unmarshal without errors\", func() {\n\t\t\t\terr := json.Unmarshal([]byte(\"{\\\"a\\\":10,\\\"b\\\":0}\"), &bnumbers)\n\t\t\t\tExpect(err).To(BeNil())\n\t\t\t})\n\t\t\tIt(\"should populate the struct properly\", func() {\n\t\t\t\tjson.Unmarshal([]byte(\"{\\\"a\\\":10,\\\"b\\\":0}\"), &bnumbers)\n\t\t\t\tExpect(bnumbers.A.IsSet).To(BeTrue())\n\t\t\t\tExpect(bnumbers.B.IsSet).To(BeTrue())\n\n\t\t\t\tExpect(bnumbers.A.Number).To(Equal(uint(10)))\n\t\t\t\tExpect(bnumbers.B.Number).To(Equal(uint(0)))\n\t\t\t})\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "client.go",
    "content": "package apns\n\nimport (\n\t\"container/list\"\n\t\"crypto/tls\"\n\t\"io\"\n\t\"log\"\n\t\"time\"\n)\n\ntype buffer struct {\n\tsize int\n\t*list.List\n}\n\nfunc newBuffer(size int) *buffer {\n\treturn &buffer{size, list.New()}\n}\n\nfunc (b *buffer) Add(v interface{}) *list.Element {\n\te := b.PushBack(v)\n\n\tif b.Len() > b.size {\n\t\tb.Remove(b.Front())\n\t}\n\n\treturn e\n}\n\ntype Client struct {\n\tConn         *Conn\n\tFailedNotifs chan NotificationResult\n\n\tnotifs chan Notification\n\tid     uint32\n}\n\nfunc newClientWithConn(gw string, conn Conn) Client {\n\tc := Client{\n\t\tConn:         &conn,\n\t\tFailedNotifs: make(chan NotificationResult),\n\t\tid:           uint32(1),\n\t\tnotifs:       make(chan Notification),\n\t}\n\n\tgo c.runLoop()\n\n\treturn c\n}\n\nfunc NewClientWithCert(gw string, cert tls.Certificate) Client {\n\tconn := NewConnWithCert(gw, cert)\n\n\treturn newClientWithConn(gw, conn)\n}\n\nfunc NewClient(gw string, cert string, key string) (Client, error) {\n\tconn, err := NewConn(gw, cert, key)\n\tif err != nil {\n\t\treturn Client{}, err\n\t}\n\n\treturn newClientWithConn(gw, conn), nil\n}\n\nfunc NewClientWithFiles(gw string, certFile string, keyFile string) (Client, error) {\n\tconn, err := NewConnWithFiles(gw, certFile, keyFile)\n\tif err != nil {\n\t\treturn Client{}, err\n\t}\n\n\treturn newClientWithConn(gw, conn), nil\n}\n\nfunc (c *Client) Send(n Notification) error {\n\tc.notifs <- n\n\treturn nil\n}\n\nfunc (c *Client) reportFailedPush(v interface{}, err *Error) {\n\tfailedNotif, ok := v.(Notification)\n\tif !ok || v == nil {\n\t\treturn\n\t}\n\n\tselect {\n\tcase c.FailedNotifs <- NotificationResult{Notif: failedNotif, Err: *err}:\n\tdefault:\n\t}\n}\n\nfunc (c *Client) requeue(cursor *list.Element) {\n\t// If `cursor` is not nil, this means there are notifications that\n\t// need to be delivered (or redelivered)\n\tfor ; cursor != nil; cursor = cursor.Next() {\n\t\tif n, ok := cursor.Value.(Notification); ok {\n\t\t\tgo func() { c.notifs <- n }()\n\t\t}\n\t}\n}\n\nfunc (c *Client) handleError(err *Error, buffer *buffer) *list.Element {\n\tcursor := buffer.Back()\n\n\tfor cursor != nil {\n\t\t// Get notification\n\t\tn, _ := cursor.Value.(Notification)\n\n\t\t// If the notification, move cursor after the trouble notification\n\t\tif n.Identifier == err.Identifier {\n\t\t\tgo c.reportFailedPush(cursor.Value, err)\n\n\t\t\tnext := cursor.Next()\n\n\t\t\tbuffer.Remove(cursor)\n\t\t\treturn next\n\t\t}\n\n\t\tcursor = cursor.Prev()\n\t}\n\n\treturn cursor\n}\n\nfunc (c *Client) runLoop() {\n\tsent := newBuffer(50)\n\tcursor := sent.Front()\n\n\t// APNS connection\n\tfor {\n\t\terr := c.Conn.Connect()\n\t\tif err != nil {\n\t\t\t// TODO Probably want to exponentially backoff...\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Start reading errors from APNS\n\t\terrs := readErrs(c.Conn)\n\n\t\tc.requeue(cursor)\n\n\t\t// Connection open, listen for notifs and errors\n\t\tfor {\n\t\t\tvar err error\n\t\t\tvar n Notification\n\n\t\t\t// Check for notifications or errors. There is a chance we'll send notifications\n\t\t\t// if we already have an error since `select` will \"pseudorandomly\" choose a\n\t\t\t// ready channels. It turns out to be fine because the connection will already\n\t\t\t// be closed and it'll requeue. We could check before we get to this select\n\t\t\t// block, but it doesn't seem worth the extra code and complexity.\n\t\t\tselect {\n\t\t\tcase err = <-errs:\n\t\t\tcase n = <-c.notifs:\n\t\t\t}\n\n\t\t\t// If there is an error we understand, find the notification that failed,\n\t\t\t// move the cursor right after it.\n\t\t\tif nErr, ok := err.(*Error); ok {\n\t\t\t\tcursor = c.handleError(nErr, sent)\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t// Add to list\n\t\t\tcursor = sent.Add(n)\n\n\t\t\t// Set identifier if not specified\n\t\t\tif n.Identifier == 0 {\n\t\t\t\tn.Identifier = c.id\n\t\t\t\tc.id++\n\t\t\t} else if c.id < n.Identifier {\n\t\t\t\tc.id = n.Identifier + 1\n\t\t\t}\n\n\t\t\tb, err := n.ToBinary()\n\t\t\tif err != nil {\n\t\t\t\t// TODO\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t_, err = c.Conn.Write(b)\n\n\t\t\tif err == io.EOF {\n\t\t\t\tlog.Println(\"EOF trying to write notification\")\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tlog.Println(\"err writing to apns\", err.Error())\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tcursor = cursor.Next()\n\t\t}\n\t}\n}\n\nfunc readErrs(c *Conn) chan error {\n\terrs := make(chan error)\n\n\tgo func() {\n\t\tp := make([]byte, 6, 6)\n\t\t_, err := c.Read(p)\n\t\tif err != nil {\n\t\t\terrs <- err\n\t\t\treturn\n\t\t}\n\n\t\te := NewError(p)\n\t\terrs <- &e\n\t}()\n\n\treturn errs\n}\n"
  },
  {
    "path": "client_test.go",
    "content": "package apns_test\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"time\"\n\t. \"github.com/onsi/ginkgo\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/timehop/apns\"\n)\n\nvar _ = Describe(\"Client\", func() {\n\tDescribe(\".NewConn\", func() {\n\t\tContext(\"bad cert/key pair\", func() {\n\t\t\tIt(\"should error out\", func() {\n\t\t\t\t_, err := apns.NewClient(apns.ProductionGateway, \"missing\", \"missing_also\")\n\t\t\t\tExpect(err).NotTo(BeNil())\n\t\t\t})\n\t\t})\n\n\t\tContext(\"valid cert/key pair\", func() {\n\t\t\tIt(\"should create a valid client\", func() {\n\t\t\t\tc, err := apns.NewClient(apns.ProductionGateway, DummyCert, DummyKey)\n\t\t\t\tExpect(err).To(BeNil())\n\t\t\t\tExpect(c.Conn).NotTo(BeNil())\n\t\t\t})\n\t\t})\n\t})\n\n\tDescribe(\".NewConnWithFiles\", func() {\n\t\tContext(\"missing cert/key pair\", func() {\n\t\t\tIt(\"should error out\", func() {\n\t\t\t\t_, err := apns.NewClientWithFiles(apns.ProductionGateway, \"missing\", \"missing_also\")\n\t\t\t\tExpect(err).NotTo(BeNil())\n\t\t\t})\n\t\t})\n\n\t\tContext(\"valid cert/key pair\", func() {\n\t\t\tvar certFile, keyFile *os.File\n\n\t\t\tBeforeEach(func() {\n\t\t\t\tcertFile, _ = ioutil.TempFile(\"\", \"cert.pem\")\n\t\t\t\tcertFile.Write([]byte(DummyCert))\n\t\t\t\tcertFile.Close()\n\n\t\t\t\tkeyFile, _ = ioutil.TempFile(\"\", \"key.pem\")\n\t\t\t\tkeyFile.Write([]byte(DummyKey))\n\t\t\t\tkeyFile.Close()\n\t\t\t})\n\n\t\t\tAfterEach(func() {\n\t\t\t\tif certFile != nil {\n\t\t\t\t\tos.Remove(certFile.Name())\n\t\t\t\t}\n\n\t\t\t\tif keyFile != nil {\n\t\t\t\t\tos.Remove(keyFile.Name())\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tIt(\"should create a valid client\", func() {\n\t\t\t\tc, err := apns.NewClientWithFiles(apns.ProductionGateway, certFile.Name(), keyFile.Name())\n\t\t\t\tExpect(err).To(BeNil())\n\t\t\t\tExpect(c.Conn).NotTo(BeNil())\n\t\t\t})\n\t\t})\n\t})\n\n\tDescribe(\"#Send\", func() {\n\t\tContext(\"simple write\", func() {\n\t\t\tas := [][]serverAction{\n\t\t\t\t[]serverAction{\n\t\t\t\t\tserverAction{action: readAction, data: []byte{}},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tIt(\"should not return an error\", func(d Done) {\n\t\t\t\tmockDone := make(chan interface{})\n\t\t\t\twithMockServerAsync(as, mockDone, func(s *mockTLSServer) {\n\t\t\t\t\tc, _ := apns.NewClient(s.Address(), DummyCert, DummyKey)\n\t\t\t\t\tc.Conn.Conf.InsecureSkipVerify = true\n\n\t\t\t\t\tExpect(c.Send(apns.Notification{})).To(BeNil())\n\n\t\t\t\t\tclose(mockDone)\n\t\t\t\t\tclose(d)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tContext(\"simple write with buffer\", func() {\n\t\t\tas := [][]serverAction{\n\t\t\t\t[]serverAction{\n\t\t\t\t\tserverAction{action: readAction, data: []byte{}},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tIt(\"should not return an error\", func(d Done) {\n\t\t\t\tmockDone := make(chan interface{})\n\t\t\t\twithMockServerAsync(as, mockDone, func(s *mockTLSServer) {\n\t\t\t\t\tc, _ := apns.NewClient(s.Address(), DummyCert, DummyKey)\n\t\t\t\t\tc.Conn.Conf.InsecureSkipVerify = true\n\n\t\t\t\t\tfor i := 0; i < 54; i++ {\n\t\t\t\t\t\tExpect(c.Send(apns.Notification{})).To(BeNil())\n\t\t\t\t\t}\n\n\t\t\t\t\tclose(mockDone)\n\t\t\t\t\tclose(d)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tContext(\"multiple write\", func() {\n\t\t\tas := [][]serverAction{\n\t\t\t\t[]serverAction{\n\t\t\t\t\tserverAction{action: readAction, data: []byte{}},\n\t\t\t\t\tserverAction{action: readAction, data: []byte{}},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tIt(\"should not return an error\", func(d Done) {\n\t\t\t\tmockDone := make(chan interface{})\n\t\t\t\twithMockServerAsync(as, mockDone, func(s *mockTLSServer) {\n\t\t\t\t\tc, _ := apns.NewClient(s.Address(), DummyCert, DummyKey)\n\t\t\t\t\tc.Conn.Conf.InsecureSkipVerify = true\n\n\t\t\t\t\tExpect(c.Send(apns.Notification{})).To(BeNil())\n\t\t\t\t\tExpect(c.Send(apns.Notification{})).To(BeNil())\n\n\t\t\t\t\tclose(mockDone)\n\t\t\t\t\tclose(d)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tContext(\"bad push\", func() {\n\t\t\tn := apns.Notification{Identifier: 9, ID: \"some_rando\"}\n\t\t\tnb, _ := n.ToBinary()\n\t\t\tnbcb := make([]byte, len(nb))\n\n\t\t\terrPayload := bytes.NewBuffer([]byte{})\n\t\t\tbinary.Write(errPayload, binary.BigEndian, uint8(8))\n\t\t\tbinary.Write(errPayload, binary.BigEndian, uint8(8))\n\t\t\tbinary.Write(errPayload, binary.BigEndian, uint32(9))\n\n\t\t\tas := [][]serverAction{\n\t\t\t\t[]serverAction{\n\t\t\t\t\tserverAction{action: readAction, data: []byte{}},\n\t\t\t\t\tserverAction{action: readAction, data: nbcb, cb: func(a serverAction) {\n\t\t\t\t\t\tExpect(a.data).To(Equal(nb))\n\t\t\t\t\t}},\n\n\t\t\t\t\t// Bad push results in a close\n\t\t\t\t\tserverAction{action: writeAction, data: errPayload.Bytes()},\n\t\t\t\t\tserverAction{action: closeAction, data: []byte{}},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tIt(\"should not return an error\", func(d Done) {\n\t\t\t\tmockDone := make(chan interface{})\n\t\t\t\twithMockServerAsync(as, mockDone, func(s *mockTLSServer) {\n\t\t\t\t\tc, _ := apns.NewClient(s.Address(), DummyCert, DummyKey)\n\t\t\t\t\tc.Conn.Conf.InsecureSkipVerify = true\n\n\t\t\t\t\tgo func() {\n\t\t\t\t\t\tn := <-c.FailedNotifs\n\n\t\t\t\t\t\tExpect(n.Notif.Identifier).To(Equal(uint32(9)))\n\t\t\t\t\t\tExpect(n.Notif.ID).To(Equal(\"some_rando\"))\n\n\t\t\t\t\t\tclose(mockDone)\n\t\t\t\t\t\tclose(d)\n\t\t\t\t\t}()\n\n\t\t\t\t\tExpect(c.Send(n)).To(BeNil())\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tContext(\"closed, reconnect\", func() {\n\t\t\tdone := make(chan bool)\n\n\t\t\tn1 := apns.Notification{Identifier: 1}\n\t\t\tn1b, _ := n1.ToBinary()\n\t\t\tn1bcb := make([]byte, len(n1b))\n\n\t\t\terrPayload := bytes.NewBuffer([]byte{})\n\t\t\tbinary.Write(errPayload, binary.BigEndian, uint8(8))\n\t\t\tbinary.Write(errPayload, binary.BigEndian, uint8(8))\n\t\t\tbinary.Write(errPayload, binary.BigEndian, uint32(2))\n\n\t\t\tIt(\"should not return an error\", func(d Done) {\n\t\t\t\tmockDone := make(chan interface{})\n\n\t\t\t\tas := [][]serverAction{\n\t\t\t\t\t[]serverAction{\n\t\t\t\t\t\t// Write error\n\t\t\t\t\t\tserverAction{action: writeAction, data: errPayload.Bytes(), cb: func(a serverAction) {\n\t\t\t\t\t\t\tdone <- true\n\t\t\t\t\t\t}},\n\n\t\t\t\t\t\t// Close on error\n\t\t\t\t\t\tserverAction{action: closeAction, cb: func(a serverAction) {\n\t\t\t\t\t\t}},\n\t\t\t\t\t},\n\t\t\t\t\t[]serverAction{\n\t\t\t\t\t\t// Reconnect\n\t\t\t\t\t\tserverAction{action: readAction, data: []byte{}, cb: func(a serverAction) {\n\t\t\t\t\t\t\t// Reconnected\n\t\t\t\t\t\t}},\n\n\t\t\t\t\t\t// Read first good notification\n\t\t\t\t\t\tserverAction{action: readAction, data: n1bcb, cb: func(a serverAction) {\n\t\t\t\t\t\t\tExpect(a.data).To(Equal(n1b))\n\n\t\t\t\t\t\t\tclose(mockDone)\n\t\t\t\t\t\t\tclose(d)\n\t\t\t\t\t\t}},\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\twithMockServerAsync(as, mockDone, func(s *mockTLSServer) {\n\t\t\t\t\tc, _ := apns.NewClient(s.Address(), DummyCert, DummyKey)\n\t\t\t\t\tc.Conn.Conf.InsecureSkipVerify = true\n\n\t\t\t\t\t<-done\n\t\t\t\t\ttime.Sleep(5 * time.Millisecond)\n\n\t\t\t\t\t// Good\n\t\t\t\t\tExpect(c.Send(n1)).To(BeNil())\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tContext(\"good, close, good, requeue of last good\", func() {\n\t\t\tclosed := make(chan bool)\n\n\t\t\tn1 := apns.Notification{Identifier: 1}\n\t\t\tn2 := apns.Notification{Identifier: 2}\n\n\t\t\tn1b, _ := n1.ToBinary()\n\t\t\tn2b, _ := n2.ToBinary()\n\n\t\t\tn1bcb := make([]byte, len(n1b))\n\t\t\tn2bcb := make([]byte, len(n2b))\n\n\t\t\tIt(\"should not return an error\", func(d Done) {\n\t\t\t\tmockDone := make(chan interface{})\n\t\t\t\tas := [][]serverAction{\n\t\t\t\t\t[]serverAction{\n\t\t\t\t\t\t// Connect\n\t\t\t\t\t\tserverAction{action: readAction, data: []byte{}, cb: func(a serverAction) {\n\t\t\t\t\t\t\t// Handshake\n\t\t\t\t\t\t}},\n\n\t\t\t\t\t\t// Read first good notification\n\t\t\t\t\t\tserverAction{action: readAction, data: n1bcb, cb: func(a serverAction) {\n\t\t\t\t\t\t\tExpect(a.data).To(Equal(n1b))\n\t\t\t\t\t\t}},\n\n\t\t\t\t\t\t// Close on error\n\t\t\t\t\t\tserverAction{action: closeAction, cb: func(a serverAction) {\n\t\t\t\t\t\t\tclosed <- true\n\t\t\t\t\t\t}},\n\t\t\t\t\t},\n\t\t\t\t\t[]serverAction{\n\t\t\t\t\t\t// Reconnect\n\t\t\t\t\t\tserverAction{action: readAction, data: []byte{}, cb: func(a serverAction) {\n\t\t\t\t\t\t\t// Reconnected\n\t\t\t\t\t\t}},\n\n\t\t\t\t\t\t// Requeue\n\t\t\t\t\t\tserverAction{action: readAction, data: n2bcb, cb: func(a serverAction) {\n\t\t\t\t\t\t\tExpect(a.data).To(Equal(n2b))\n\n\t\t\t\t\t\t\tclose(mockDone)\n\t\t\t\t\t\t\tclose(d)\n\t\t\t\t\t\t}},\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\twithMockServerAsync(as, mockDone, func(s *mockTLSServer) {\n\t\t\t\t\tc, _ := apns.NewClient(s.Address(), DummyCert, DummyKey)\n\t\t\t\t\tc.Conn.Conf.InsecureSkipVerify = true\n\n\t\t\t\t\t// Good\n\t\t\t\t\tExpect(c.Send(n1)).To(BeNil())\n\n\t\t\t\t\t<-closed\n\t\t\t\t\ttime.Sleep(5 * time.Millisecond)\n\n\t\t\t\t\t// Good\n\t\t\t\t\tExpect(c.Send(n2)).To(BeNil())\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tContext(\"good, bad, good, requeue of last good\", func() {\n\t\t\tIt(\"should not return an error\", func(d Done) {\n\t\t\t\tmockDone := make(chan interface{})\n\n\t\t\t\tn1 := apns.Notification{Identifier: 1}\n\t\t\t\tn2 := apns.Notification{Identifier: 2}\n\t\t\t\tn3 := apns.Notification{Identifier: 3}\n\n\t\t\t\tn1b, _ := n1.ToBinary()\n\t\t\t\tn2b, _ := n2.ToBinary()\n\t\t\t\tn3b, _ := n3.ToBinary()\n\n\t\t\t\tn1bcb := make([]byte, len(n1b))\n\t\t\t\tn2bcb := make([]byte, len(n2b))\n\t\t\t\tn3bcb := make([]byte, len(n3b))\n\n\t\t\t\terrPayload := bytes.NewBuffer([]byte{})\n\t\t\t\tbinary.Write(errPayload, binary.BigEndian, uint8(8))\n\t\t\t\tbinary.Write(errPayload, binary.BigEndian, uint8(8))\n\t\t\t\tbinary.Write(errPayload, binary.BigEndian, uint32(2))\n\n\t\t\t\tas := [][]serverAction{\n\t\t\t\t\t[]serverAction{\n\t\t\t\t\t\t// Connect\n\t\t\t\t\t\tserverAction{action: readAction, data: []byte{}, cb: func(a serverAction) {\n\t\t\t\t\t\t\t// Handshake\n\t\t\t\t\t\t}},\n\n\t\t\t\t\t\t// Read first good notification\n\t\t\t\t\t\tserverAction{action: readAction, data: n1bcb, cb: func(a serverAction) {\n\t\t\t\t\t\t\tExpect(a.data).To(Equal(n1b))\n\t\t\t\t\t\t}},\n\n\t\t\t\t\t\t// Read bad notification\n\t\t\t\t\t\tserverAction{action: readAction, data: n2bcb, cb: func(a serverAction) {\n\t\t\t\t\t\t\tExpect(a.data).To(Equal(n2b))\n\t\t\t\t\t\t}},\n\n\t\t\t\t\t\t// Read second good notification\n\t\t\t\t\t\tserverAction{action: readAction, data: n3bcb, cb: func(a serverAction) {\n\t\t\t\t\t\t\tExpect(a.data).To(Equal(n3b))\n\t\t\t\t\t\t}},\n\n\t\t\t\t\t\t// Write error\n\t\t\t\t\t\tserverAction{action: writeAction, data: errPayload.Bytes(), cb: func(a serverAction) {\n\t\t\t\t\t\t}},\n\n\t\t\t\t\t\t// Close on error\n\t\t\t\t\t\tserverAction{action: closeAction, cb: func(a serverAction) {\n\t\t\t\t\t\t}},\n\t\t\t\t\t},\n\t\t\t\t\t[]serverAction{\n\t\t\t\t\t\t// Reconnect\n\t\t\t\t\t\tserverAction{action: readAction, data: []byte{}, cb: func(a serverAction) {\n\t\t\t\t\t\t\t// Reconnected\n\t\t\t\t\t\t}},\n\n\t\t\t\t\t\t// Requeue\n\t\t\t\t\t\tserverAction{action: readAction, data: n3bcb, cb: func(a serverAction) {\n\t\t\t\t\t\t\tExpect(a.data).To(Equal(n3b))\n\n\t\t\t\t\t\t\tclose(mockDone)\n\t\t\t\t\t\t\tclose(d)\n\t\t\t\t\t\t}},\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\twithMockServerAsync(as, mockDone, func(s *mockTLSServer) {\n\t\t\t\t\tc, _ := apns.NewClient(s.Address(), DummyCert, DummyKey)\n\t\t\t\t\tc.Conn.Conf.InsecureSkipVerify = true\n\n\t\t\t\t\t// Good\n\t\t\t\t\tExpect(c.Send(n1)).To(BeNil())\n\n\t\t\t\t\t// Bad\n\t\t\t\t\tExpect(c.Send(n2)).To(BeNil())\n\n\t\t\t\t\t// Good\n\t\t\t\t\tExpect(c.Send(n3)).To(BeNil())\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "conn.go",
    "content": "package apns\n\nimport (\n\t\"crypto/tls\"\n\t\"net\"\n\t\"strings\"\n)\n\nconst (\n\tProductionGateway = \"gateway.push.apple.com:2195\"\n\tSandboxGateway    = \"gateway.sandbox.push.apple.com:2195\"\n\n\tProductionFeedbackGateway = \"feedback.push.apple.com:2196\"\n\tSandboxFeedbackGateway    = \"feedback.sandbox.push.apple.com:2196\"\n)\n\n// Conn is a wrapper for the actual TLS connections made to Apple\ntype Conn struct {\n\tNetConn net.Conn\n\tConf    *tls.Config\n\n\tgateway   string\n\tconnected bool\n}\n\nfunc NewConnWithCert(gw string, cert tls.Certificate) Conn {\n\tgatewayParts := strings.Split(gw, \":\")\n\tconf := tls.Config{\n\t\tCertificates: []tls.Certificate{cert},\n\t\tServerName:   gatewayParts[0],\n\t}\n\n\treturn Conn{gateway: gw, Conf: &conf}\n}\n\n// NewConnWithFiles creates a new Conn from certificate and key in the specified files\nfunc NewConn(gw string, crt string, key string) (Conn, error) {\n\tcert, err := tls.X509KeyPair([]byte(crt), []byte(key))\n\tif err != nil {\n\t\treturn Conn{}, err\n\t}\n\n\treturn NewConnWithCert(gw, cert), nil\n}\n\n// NewConnWithFiles creates a new Conn from certificate and key in the specified files\nfunc NewConnWithFiles(gw string, certFile string, keyFile string) (Conn, error) {\n\tcert, err := tls.LoadX509KeyPair(certFile, keyFile)\n\tif err != nil {\n\t\treturn Conn{}, err\n\t}\n\n\treturn NewConnWithCert(gw, cert), nil\n}\n\n// Connect actually creates the TLS connection\nfunc (c *Conn) Connect() error {\n\t// Make sure the existing connection is closed\n\tif c.NetConn != nil {\n\t\tc.NetConn.Close()\n\t}\n\n\tconn, err := net.Dial(\"tcp\", c.gateway)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttlsConn := tls.Client(conn, c.Conf)\n\terr = tlsConn.Handshake()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tc.NetConn = tlsConn\n\treturn nil\n}\n\nfunc (c *Conn) Close() error {\n\tif c.NetConn != nil {\n\t\treturn c.NetConn.Close()\n\t}\n\n\treturn nil\n}\n\n// Read reads data from the connection\nfunc (c *Conn) Read(p []byte) (int, error) {\n\ti, err := c.NetConn.Read(p)\n\treturn i, err\n}\n\n// Write writes data from the connection\nfunc (c *Conn) Write(p []byte) (int, error) {\n\treturn c.NetConn.Write(p)\n}\n"
  },
  {
    "path": "conn_test.go",
    "content": "package apns_test\n\nimport (\n\t\"bytes\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\t. \"github.com/onsi/ginkgo\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/timehop/apns\"\n)\n\nvar DummyCert = `-----BEGIN CERTIFICATE-----\nMIIC9TCCAd+gAwIBAgIQf3bEgFWUb+q6eK5ySkV/gjALBgkqhkiG9w0BAQUwEjEQ\nMA4GA1UEChMHQWNtZSBDbzAeFw0xNDA2MzAwNDI5MDhaFw0xNTA2MzAwNDI5MDha\nMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\nAoIBAQDhAgWrrFZBtCfVEPg1tSIr9fuSUoeundb556IUr9uOmOHaYK7r3/I43acw\nbVIfaenFxwUUf8YakQzTjOa5qSfK/Eylyw2ezBJtNUEqcHw0f+y66+jJbZa4clPa\ntL6ezaMS/syXPpvNU8+16jdVdTJzqdBdSGAZMOCeumUWDNdlfBmHPVq1JMy0uGmO\nXDoZK2Ir0/3LUfjk9R2wdm1VLrJAml7F0L0FhBHHXgHOSFM2ixjGflffaiuTCxhW\n1z1NTo9XjWUQh2iM9Udf+xVnJLGLZ0EMFr2qihuK604Fp4SlNHEF+UWUn+j0PYo+\nLbzM9oKJcdVD0XI36vrn3rGPHO9vAgMBAAGjSzBJMA4GA1UdDwEB/wQEAwIAoDAT\nBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuCCWxv\nY2FsaG9zdDALBgkqhkiG9w0BAQUDggEBAGJ/3I4KKlbEwLAC5ut4ZZ9V8WF4sHkI\nLj7e4vx2pPi6hf9miV1ff01NrpfUna7flwL9yD7Ybl7jRRIB4rIcKk+U5djGsT3H\nScGkbIMKrr08drWw1g4JU6PBH7xTfzGxNRERrnmrbJV0jCo9Tt8i53IpPtp6Z2Q1\n8ydtPhU+Bpe2YoNr1w1fSV1JHXqjKV8RlGkCNSi4ozPOO8RbAYnBT3d9XSGoX//q\nRGJUf3wC/rCxJkN63Moxuy3vxV2TmiqccHOrXJSJ8P/4PpPV/xuBk5k4HS1Nfmew\nd9WHHn6bMJE9arVvWAiu9teCadVffuS2cl2cicN4XB6Ui0aDqhG2Exw=\n-----END CERTIFICATE-----`\n\nvar DummyKey = `-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA4QIFq6xWQbQn1RD4NbUiK/X7klKHrp3W+eeiFK/bjpjh2mCu\n69/yON2nMG1SH2npxccFFH/GGpEM04zmuaknyvxMpcsNnswSbTVBKnB8NH/suuvo\nyW2WuHJT2rS+ns2jEv7Mlz6bzVPPteo3VXUyc6nQXUhgGTDgnrplFgzXZXwZhz1a\ntSTMtLhpjlw6GStiK9P9y1H45PUdsHZtVS6yQJpexdC9BYQRx14BzkhTNosYxn5X\n32orkwsYVtc9TU6PV41lEIdojPVHX/sVZySxi2dBDBa9qoobiutOBaeEpTRxBflF\nlJ/o9D2KPi28zPaCiXHVQ9FyN+r6596xjxzvbwIDAQABAoIBAFzW+cIA5MJNdFX8\nn32BlGzxHPEd7nAFHmuUwJKqkPwAZsg1NleK2qXOByr7IHRnvhZl7Nmtcu8JRHKR\nY63ddtbRTUrnQmJwL3YyEAZTzVvYILRrnGxoNFU8jw7hnvllPdEbow0QvzZ0S3Lz\nBgvTxJJm0dt7fnNGcJftrsHvYHy1dptaR4hPv0xV5G7RPrbTl94llKfi745tp5Wd\nxGpnjcBXoAnzCVRij1tHfSYubRJ2MJV0kzG3oVdRV2P/zWaout8BlhLCURv4sRUX\n7FfCNa/z+G6AlROjCKJUP9YIUbxBEa/aP8YlSiyLRi1jFbMWcnKWQUdqS19m73Ap\na1LJFPECgYEA+Ve5DegcrWnUb2HsHD38HlmEg6S+/jg2P4TsuLZBtvO4/vzRx/qq\npwuuMm2CsvXr4nVmMEsMlSzYdsnaXIlWqyVDCOwIWR5VYT2GDWqQLaIXPlFaISzN\n27tHd64KUtR1fMJUwQVK/MUORUbpYoAnSIil2SlYkWUhF024fNP8CxcCgYEA5wP4\nHLiqU2rqe7vSAF/8fHwPleTzuCfMCVZm0aegUzQQQtklZoVE/BBwEGHdXflq1veq\npHeC8bNR4BF6ZgeSWgbLVF3msquy47QeNElHA2muJd3qmNWz4LXo1Pxb8KXcnXri\nQZ+r3Y8obWTFQYq7gGQGPLXGTV3bhLGIyrT4lWkCgYAgZ2MYSJL5gmhmNT6fCPsr\n4oxTI2Ti2uFJ7fdppd3ybcgb8zU8HPpyjRUNXqf+o/EM1B78pbQz6skS3vau0fZe\ndZA5p5sKIeQMqBc0xSWJmKgWpDHnX9A8/yCxj/+tdgjytrqW/x4YrW9GV4nbEDaK\nuZ98EmB9PLxJMAOKzW3S7wKBgQDD4PCy4b3CR2iVC9dva/P5VXQdo+knX884p6M8\n58YgZofXNqnouN2aYRG0QlbiBMcbiRqOo6tK58JnnEpNUuQ8I4Cqg4hGPSHMwv/N\nU8i70xLPltABUUpZIcVPOr92WBytBvHrtMiUb3tW7lf3T/vWTHmhZnvDQ+8LH0Ge\npz4T6QKBgQCoBJKOd781IQmT6i5hHSYJlsP6ymaaaQniJPVpnci/jf8+2QtponQY\nscgnaBLBasLQ6GfKSRtcyidEi9wwxpVj0tw2p567jeNcIveD0TOYFf0RHEfrs+D4\nVdRgai/v2NbFZLDnzeGVuYypXu6R78isJfHtz/a0aEave8yB3CRiDw==\n-----END RSA PRIVATE KEY-----`\n\n// To be able to run in parallel\nvar mockPort = 50000\n\n// Mock Addr\ntype mockAddr struct {\n}\n\nfunc (m mockAddr) Network() string {\n\treturn \"localhost:56789\"\n}\n\nfunc (m mockAddr) String() string {\n\treturn \"localhost:56789\"\n}\n\n// Mock TLS connection\ntype mockTLSNetConn struct {\n\tbb  *bytes.Buffer\n\terr error\n}\n\nfunc (t mockTLSNetConn) Read(p []byte) (int, error) {\n\tr := bytes.NewReader(t.bb.Bytes())\n\treturn r.Read(p)\n}\n\nfunc (t mockTLSNetConn) Write(p []byte) (int, error) {\n\treturn t.bb.Write(p)\n}\n\nfunc (t mockTLSNetConn) Close() error {\n\treturn t.err\n}\n\nfunc (m mockTLSNetConn) LocalAddr() net.Addr {\n\treturn mockAddr{}\n}\n\nfunc (m mockTLSNetConn) RemoteAddr() net.Addr {\n\treturn mockAddr{}\n}\n\nfunc (m mockTLSNetConn) SetDeadline(t time.Time) error {\n\treturn nil\n}\n\nfunc (m mockTLSNetConn) SetReadDeadline(t time.Time) error {\n\treturn nil\n}\n\nfunc (m mockTLSNetConn) SetWriteDeadline(t time.Time) error {\n\treturn nil\n}\n\ntype serverAction struct {\n\taction string\n\tdata   []byte\n\tcb     func(s serverAction)\n}\n\nconst (\n\treadAction  = \"read\"\n\twriteAction = \"write\"\n\tcloseAction = \"close\"\n)\n\ntype mockTLSServer struct {\n\tPort                   int\n\tServer                 net.Listener\n\tConnectionActionGroups [][]serverAction\n}\n\nfunc (m *mockTLSServer) portStr() string {\n\tif m.Port == 0 {\n\t\tmockPort = mockPort + 1\n\t\tm.Port = mockPort\n\t}\n\n\treturn fmt.Sprint(m.Port)\n}\n\nfunc (m *mockTLSServer) Address() string {\n\treturn \"localhost:\" + m.portStr()\n}\n\nfunc (m *mockTLSServer) start() {\n\tcert, err := tls.X509KeyPair([]byte(DummyCert), []byte(DummyKey))\n\tif err != nil {\n\t\tlog.Panic(err)\n\t}\n\n\tconfig := tls.Config{Certificates: []tls.Certificate{cert}, ClientAuth: tls.RequireAnyClientCert}\n\n\tm.Server, err = tls.Listen(\"tcp\", \"localhost:\"+m.portStr(), &config)\n\tgo func() {\n\t\tfor i := 0; i < len(m.ConnectionActionGroups); i++ {\n\t\t\tg := m.ConnectionActionGroups[i]\n\n\t\t\t// Wait for a connection.\n\t\t\tconn, err := m.Server.Accept()\n\t\t\tif err != nil {\n\t\t\t\tif strings.Contains(err.Error(), \"use of closed network connection\") {\n\t\t\t\t\treturn\n\t\t\t\t} else {\n\t\t\t\t\tlog.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Handle the connection in a new goroutine.\n\t\t\t// The loop then returns to accepting, so that\n\t\t\t// multiple connections may be served concurrently.\n\t\t\tgo func(c net.Conn) {\n\t\t\t\tfor j := 0; j < len(g); j++ {\n\t\t\t\t\ta := g[j]\n\t\t\t\t\tswitch a.action {\n\t\t\t\t\tcase readAction:\n\t\t\t\t\t\tc.Read(a.data)\n\t\t\t\t\tcase writeAction:\n\t\t\t\t\t\tc.Write(a.data)\n\t\t\t\t\tcase closeAction:\n\t\t\t\t\t\tc.Close()\n\n\t\t\t\t\t\tif a.cb != nil {\n\t\t\t\t\t\t\ta.cb(a)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tif a.cb != nil {\n\t\t\t\t\t\ta.cb(a)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}(conn)\n\t\t}\n\n\t\t// No more connection action groups\n\t}()\n}\n\nfunc (m *mockTLSServer) stop() {\n\tif m.Server != nil {\n\t\tm.Server.Close()\n\t}\n}\n\nvar withMockServer = func(as [][]serverAction, cb func(s *mockTLSServer)) {\n\td := make(chan interface{})\n\twithMockServerAsync(as, d, func(s *mockTLSServer) {\n\t\tcb(s)\n\t\tclose(d)\n\t})\n}\n\nvar withMockServerAsync = func(as [][]serverAction, d chan interface{}, cb func(s *mockTLSServer)) {\n\ts := &mockTLSServer{}\n\ts.ConnectionActionGroups = as\n\n\ts.start()\n\n\tcb(s)\n\n\t<-d\n\ts.stop()\n}\n\n// Tests\nvar _ = Describe(\"Conn\", func() {\n\tDescribe(\".NewConn\", func() {\n\t\tContext(\"bad key/cert pair\", func() {\n\t\t\tIt(\"should return an error\", func() {\n\t\t\t\t_, err := apns.NewConn(apns.SandboxGateway, \"missing\", \"missing\")\n\t\t\t\tExpect(err).NotTo(BeNil())\n\t\t\t})\n\t\t})\n\n\t\tContext(\"valid key/cert pair\", func() {\n\t\t\tIt(\"should not return an error\", func() {\n\t\t\t\t_, err := apns.NewConn(apns.SandboxGateway, DummyCert, DummyKey)\n\t\t\t\tExpect(err).To(BeNil())\n\t\t\t})\n\t\t})\n\t})\n\n\tDescribe(\".NewConnWithFiles\", func() {\n\t\tContext(\"missing files\", func() {\n\t\t\tIt(\"should return an error\", func() {\n\t\t\t\t_, err := apns.NewConnWithFiles(apns.SandboxGateway, \"missing.pem\", \"missing.pem\")\n\t\t\t\tExpect(err).NotTo(BeNil())\n\t\t\t})\n\t\t})\n\n\t\tContext(\"with valid cert/key pair\", func() {\n\t\t\tvar certFile, keyFile *os.File\n\t\t\tvar err error\n\n\t\t\tBeforeEach(func() {\n\t\t\t\tcertFile, _ = ioutil.TempFile(\"\", \"cert.pem\")\n\t\t\t\tcertFile.Write([]byte(DummyCert))\n\t\t\t\tcertFile.Close()\n\n\t\t\t\tkeyFile, _ = ioutil.TempFile(\"\", \"key.pem\")\n\t\t\t\tkeyFile.Write([]byte(DummyKey))\n\t\t\t\tkeyFile.Close()\n\t\t\t})\n\n\t\t\tAfterEach(func() {\n\t\t\t\tif certFile != nil {\n\t\t\t\t\tos.Remove(certFile.Name())\n\t\t\t\t}\n\n\t\t\t\tif keyFile != nil {\n\t\t\t\t\tos.Remove(keyFile.Name())\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tIt(\"should returning a connection\", func() {\n\t\t\t\t_, err = apns.NewConnWithFiles(apns.SandboxGateway, certFile.Name(), keyFile.Name())\n\t\t\t\tExpect(err).To(BeNil())\n\t\t\t})\n\t\t})\n\t})\n\n\tDescribe(\"#Connect()\", func() {\n\t\tContext(\"server not up\", func() {\n\t\t\tconn, _ := apns.NewConnWithFiles(apns.SandboxGateway, \"missing.pem\", \"missing.pem\")\n\n\t\t\tIt(\"should return an error\", func() {\n\t\t\t\terr := conn.Connect()\n\t\t\t\tExpect(err).NotTo(BeNil())\n\t\t\t})\n\t\t})\n\n\t\tContext(\"server up\", func() {\n\t\t\tas := [][]serverAction{[]serverAction{serverAction{action: readAction, data: []byte{}}}}\n\n\t\t\tContext(\"with untrusted certs\", func() {\n\t\t\t\tIt(\"should return an error\", func(d Done) {\n\t\t\t\t\twithMockServer(as, func(s *mockTLSServer) {\n\t\t\t\t\t\tconn, _ := apns.NewConn(s.Address(), DummyCert, DummyKey)\n\t\t\t\t\t\terr := conn.Connect()\n\t\t\t\t\t\tExpect(err).NotTo(BeNil())\n\n\t\t\t\t\t\tclose(d)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tContext(\"trusting the certs\", func() {\n\t\t\t\tIt(\"should not return an error\", func(d Done) {\n\t\t\t\t\twithMockServer(as, func(s *mockTLSServer) {\n\t\t\t\t\t\tconn, _ := apns.NewConn(s.Address(), DummyCert, DummyKey)\n\t\t\t\t\t\tconn.Conf.InsecureSkipVerify = true\n\n\t\t\t\t\t\terr := conn.Connect()\n\t\t\t\t\t\tExpect(err).To(BeNil())\n\n\t\t\t\t\t\tclose(d)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tContext(\"with existing connection\", func() {\n\t\t\t\tIt(\"should not return an error\", func(d Done) {\n\t\t\t\t\tas = [][]serverAction{\n\t\t\t\t\t\t[]serverAction{serverAction{action: readAction, data: []byte{}}},\n\t\t\t\t\t\t[]serverAction{serverAction{action: readAction, data: []byte{}}},\n\t\t\t\t\t}\n\n\t\t\t\t\twithMockServer(as, func(s *mockTLSServer) {\n\t\t\t\t\t\tconn, _ := apns.NewConn(s.Address(), DummyCert, DummyKey)\n\t\t\t\t\t\tconn.Conf.InsecureSkipVerify = true\n\n\t\t\t\t\t\tconn.Connect()\n\n\t\t\t\t\t\terr := conn.Connect()\n\t\t\t\t\t\tExpect(err).To(BeNil())\n\n\t\t\t\t\t\tclose(d)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t})\n\n\tDescribe(\"#Read\", func() {\n\t\trwc := mockTLSNetConn{bb: bytes.NewBuffer([]byte(\"hello!\"))}\n\n\t\tpp := make([]byte, 6)\n\t\tbytes.NewReader(rwc.bb.Bytes()).Read(pp)\n\n\t\tconn, _ := apns.NewConn(apns.ProductionGateway, DummyCert, DummyKey)\n\t\tconn.NetConn = rwc\n\n\t\tIt(\"should read out 'hello!'\", func() {\n\t\t\tp := make([]byte, 6)\n\t\t\tconn.Read(p)\n\n\t\t\tExpect(p).To(Equal([]byte(\"hello!\")))\n\t\t})\n\t})\n\n\tDescribe(\"#Write\", func() {\n\t\trwc := mockTLSNetConn{bb: bytes.NewBuffer([]byte{})}\n\n\t\tconn, _ := apns.NewConn(apns.ProductionGateway, DummyCert, DummyKey)\n\t\tconn.NetConn = rwc\n\n\t\tIt(\"should write out 'world!'\", func() {\n\t\t\tconn.Write([]byte(\"world!\"))\n\t\t\tExpect(rwc.bb.String()).To(Equal(\"world!\"))\n\t\t})\n\t})\n\n\tDescribe(\"#Close\", func() {\n\t\tContext(\"with connection\", func() {\n\t\t\tContext(\"no error\", func() {\n\t\t\t\trwc := mockTLSNetConn{bb: bytes.NewBuffer([]byte{})}\n\n\t\t\t\tconn, _ := apns.NewConn(apns.ProductionGateway, DummyCert, DummyKey)\n\t\t\t\tconn.NetConn = rwc\n\n\t\t\t\tIt(\"should return no error\", func() {\n\t\t\t\t\tExpect(rwc.Close()).To(BeNil())\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tContext(\"with error\", func() {\n\t\t\t\trwc := mockTLSNetConn{bb: bytes.NewBuffer([]byte{})}\n\n\t\t\t\tconn, _ := apns.NewConn(apns.ProductionGateway, DummyCert, DummyKey)\n\t\t\t\tconn.NetConn = rwc\n\n\t\t\t\trwc.err = io.EOF\n\t\t\t\tIt(\"should return that error\", func() {\n\t\t\t\t\tExpect(rwc.Close()).To(Equal(io.EOF))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tContext(\"without connection\", func() {\n\t\t\tc, _ := apns.NewConn(apns.ProductionGateway, DummyCert, DummyKey)\n\t\t\tIt(\"should not return an error\", func() {\n\t\t\t\tExpect(c.Close()).To(BeNil())\n\t\t\t})\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "doc.go",
    "content": "/*\nA Go package to interface with the Apple Push\nNotification Service\n\nFeatures\n\nThis library implements a few features that we couldn't find in any one\nlibrary elsewhere:\n\n  Long Lived Clients     - Apple's documentation say that you should hold a\n                           persistent connection open and not create new\n                           connections for every payload\n                           See: https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW6)\n\n  Use of New Protocol    - Apple came out with v2 of their API with support for\n                           variable length payloads. This library uses that\n                           protocol.\n\n  Robust Send Guarantees - APNS has asynchronous feedback on whether a push\n                           sent. That means that if you send pushes after a\n                           bad send, those pushes will be lost forever. Our\n                           library records the last N pushes, detects errors,\n                           and is able to resend the pushes that could have\n                           been lost.\n                           See: http://redth.codes/the-problem-with-apples-push-notification-ser/\n*/\npackage apns\n"
  },
  {
    "path": "error.go",
    "content": "package apns\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n)\n\nconst (\n\t// Error strings based on the codes specified here:\n\t// https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW12\n\tErrProcessing         = \"Processing error\"\n\tErrMissingDeviceToken = \"Missing device token\"\n\tErrMissingTopic       = \"Missing topic\"\n\tErrMissingPayload     = \"Missing payload\"\n\tErrInvalidTokenSize   = \"Invalid token size\"\n\tErrInvalidTopicSize   = \"Invalid topic size\"\n\tErrInvalidPayloadSize = \"Invalid payload size\"\n\tErrInvalidToken       = \"Invalid token\"\n\tErrShutdown           = \"Shutdown\"\n\tErrUnknown            = \"None (unknown)\"\n)\n\nvar errorMapping = map[uint8]string{\n\t1:   ErrProcessing,\n\t2:   ErrMissingDeviceToken,\n\t3:   ErrMissingTopic,\n\t4:   ErrMissingPayload,\n\t5:   ErrInvalidTokenSize,\n\t6:   ErrInvalidTopicSize,\n\t7:   ErrInvalidPayloadSize,\n\t8:   ErrInvalidToken,\n\t10:  ErrShutdown,\n\t255: ErrUnknown,\n}\n\ntype Error struct {\n\tCommand    uint8\n\tStatus     uint8\n\tIdentifier uint32\n\tErrStr     string\n}\n\nfunc NewError(p []byte) Error {\n\tif len(p) != 1+1+4 {\n\t\treturn Error{ErrStr: ErrUnknown}\n\t}\n\n\tr := bytes.NewBuffer(p)\n\te := Error{}\n\n\tbinary.Read(r, binary.BigEndian, &e.Command)\n\tbinary.Read(r, binary.BigEndian, &e.Status)\n\tbinary.Read(r, binary.BigEndian, &e.Identifier)\n\n\tvar ok bool\n\tif e.ErrStr, ok = errorMapping[e.Status]; !ok {\n\t\te.ErrStr = ErrUnknown\n\t}\n\n\treturn e\n}\n\nfunc (e *Error) Error() string {\n\treturn e.ErrStr\n}\n"
  },
  {
    "path": "error_test.go",
    "content": "package apns_test\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"math/rand\"\n\n\t. \"github.com/onsi/ginkgo\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/timehop/apns\"\n)\n\nvar _ = Describe(\"Error\", func() {\n\tDescribe(\".NewError\", func() {\n\t\tShouldBeErrorWithErrStr := func(status int, errStr string) {\n\t\t\tvar errPayload = func(command int, status int, identifier int) []byte {\n\t\t\t\tbuffer := bytes.NewBuffer([]byte{})\n\t\t\t\tbinary.Write(buffer, binary.BigEndian, uint8(command))\n\t\t\t\tbinary.Write(buffer, binary.BigEndian, uint8(status))\n\t\t\t\tbinary.Write(buffer, binary.BigEndian, uint32(identifier))\n\t\t\t\treturn buffer.Bytes()\n\t\t\t}\n\n\t\t\tcommand := rand.Int()\n\t\t\tidentifier := rand.Int()\n\n\t\t\tp := errPayload(command, status, identifier)\n\t\t\te := apns.NewError(p)\n\n\t\t\tIt(\"should parse the field out correctly\", func() {\n\t\t\t\tExpect(e.Command).To(Equal(uint8(command)))\n\t\t\t\tExpect(e.Status).To(Equal(uint8(status)))\n\t\t\t\tExpect(e.Identifier).To(Equal(uint32(identifier)))\n\t\t\t})\n\n\t\t\tIt(\"should have picked the right error string\", func() {\n\t\t\t\tExpect(e.ErrStr).To(Equal(errStr))\n\t\t\t})\n\t\t}\n\n\t\tContext(\"processing error\", func() {\n\t\t\tShouldBeErrorWithErrStr(1, apns.ErrProcessing)\n\t\t})\n\n\t\tContext(\"device token error\", func() {\n\t\t\tShouldBeErrorWithErrStr(2, apns.ErrMissingDeviceToken)\n\t\t})\n\n\t\tContext(\"missing topic error\", func() {\n\t\t\tShouldBeErrorWithErrStr(3, apns.ErrMissingTopic)\n\t\t})\n\n\t\tContext(\"missing payload error\", func() {\n\t\t\tShouldBeErrorWithErrStr(4, apns.ErrMissingPayload)\n\t\t})\n\n\t\tContext(\"invalid token size error\", func() {\n\t\t\tShouldBeErrorWithErrStr(5, apns.ErrInvalidTokenSize)\n\t\t})\n\n\t\tContext(\"invalid topic size error\", func() {\n\t\t\tShouldBeErrorWithErrStr(6, apns.ErrInvalidTopicSize)\n\t\t})\n\n\t\tContext(\"invalid payload size error\", func() {\n\t\t\tShouldBeErrorWithErrStr(7, apns.ErrInvalidPayloadSize)\n\t\t})\n\n\t\tContext(\"invalid token error\", func() {\n\t\t\tShouldBeErrorWithErrStr(8, apns.ErrInvalidToken)\n\t\t})\n\n\t\tContext(\"shutdown error\", func() {\n\t\t\tShouldBeErrorWithErrStr(10, apns.ErrShutdown)\n\t\t})\n\n\t\tContext(\"unknown error\", func() {\n\t\t\tShouldBeErrorWithErrStr(255, apns.ErrUnknown)\n\t\t})\n\n\t\tContext(\"error with unrecognized code\", func() {\n\t\t\tShouldBeErrorWithErrStr(300, apns.ErrUnknown)\n\t\t})\n\n\t\tContext(\"not enough bytes\", func() {\n\t\t\tIt(\"should be ErrUnknown\", func() {\n\t\t\t\te := apns.NewError([]byte{})\n\t\t\t\tExpect(e).NotTo(BeNil())\n\t\t\t\tExpect(e.ErrStr).To(Equal(apns.ErrUnknown))\n\t\t\t})\n\t\t})\n\t})\n\n\tDescribe(\"#Error\", func() {\n\t\tIt(\"should have an error string\", func() {\n\t\t\te := apns.Error{ErrStr: \"this is an error string\"}\n\t\t\tExpect(e.Error()).To(Equal(\"this is an error string\"))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "example/example.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\n\t\"github.com/timehop/apns\"\n)\n\nfunc main() {\n\tc, err := apns.NewClientWithFiles(apns.ProductionGateway, \"apns.crt\", \"apns.key\")\n\tif err != nil {\n\t\tlog.Fatal(\"Could not create client\", err.Error())\n\t}\n\n\ti := 0\n\tfor {\n\t\tfmt.Print(\"Enter '<token> <badge> <msg>': \")\n\n\t\tvar tok, body string\n\t\tvar badge uint\n\n\t\t_, err := fmt.Scanf(\"%s %d %s\", &tok, &badge, &body)\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"Something went wrong: %v\\n\", err.Error())\n\t\t\tcontinue\n\t\t}\n\n\t\tp := apns.NewPayload()\n\t\tp.APS.Alert.Body = body\n\t\tp.APS.Badge.Set(badge)\n\n\t\tp.SetCustomValue(\"link\", \"yourapp://precache/20140718\")\n\n\t\tm := apns.NewNotification()\n\t\tm.Payload = p\n\t\tm.DeviceToken = tok\n\t\tm.Priority = apns.PriorityImmediate\n\t\tm.Identifier = uint32(i)\n\n\t\tc.Send(m)\n\n\t\ti++\n\t}\n}\n"
  },
  {
    "path": "feedback.go",
    "content": "package apns\n\nimport (\n\t\"bytes\"\n\t\"crypto/tls\"\n\t\"encoding/binary\"\n\t\"encoding/hex\"\n\t\"time\"\n)\n\ntype Feedback struct {\n\tConn *Conn\n}\n\ntype FeedbackTuple struct {\n\tTimestamp   time.Time\n\tTokenLength uint16\n\tDeviceToken string\n}\n\nfunc feedbackTupleFromBytes(b []byte) FeedbackTuple {\n\tr := bytes.NewReader(b)\n\n\tvar ts uint32\n\tbinary.Read(r, binary.BigEndian, &ts)\n\n\tvar tokLen uint16\n\tbinary.Read(r, binary.BigEndian, &tokLen)\n\n\ttok := make([]byte, tokLen)\n\tbinary.Read(r, binary.BigEndian, &tok)\n\n\treturn FeedbackTuple{\n\t\tTimestamp:   time.Unix(int64(ts), 0),\n\t\tTokenLength: tokLen,\n\t\tDeviceToken: hex.EncodeToString(tok),\n\t}\n}\n\nfunc NewFeedbackWithCert(gw string, cert tls.Certificate) Feedback {\n\tconn := NewConnWithCert(gw, cert)\n\n\treturn Feedback{Conn: &conn}\n}\n\nfunc NewFeedback(gw string, cert string, key string) (Feedback, error) {\n\tconn, err := NewConn(gw, cert, key)\n\tif err != nil {\n\t\treturn Feedback{}, err\n\t}\n\n\treturn Feedback{Conn: &conn}, nil\n}\n\nfunc NewFeedbackWithFiles(gw string, certFile string, keyFile string) (Feedback, error) {\n\tconn, err := NewConnWithFiles(gw, certFile, keyFile)\n\tif err != nil {\n\t\treturn Feedback{}, err\n\t}\n\n\treturn Feedback{Conn: &conn}, nil\n}\n\n// Receive returns a read only channel for APNs feedback. The returned channel\n// will close when there is no more data to be read.\nfunc (f Feedback) Receive() <-chan FeedbackTuple {\n\tfc := make(chan FeedbackTuple)\n\tgo f.receive(fc)\n\treturn fc\n}\n\nfunc (f Feedback) receive(fc chan FeedbackTuple) {\n\terr := f.Conn.Connect()\n\tif err != nil {\n\t\tclose(fc)\n\t\treturn\n\t}\n\tdefer f.Conn.Close()\n\n\tfor {\n\t\tb := make([]byte, 38)\n\n\t\tf.Conn.NetConn.SetReadDeadline(time.Now().Add(100 * time.Millisecond))\n\n\t\t_, err := f.Conn.Read(b)\n\t\tif err != nil {\n\t\t\tclose(fc)\n\t\t\treturn\n\t\t}\n\n\t\tfc <- feedbackTupleFromBytes(b)\n\t}\n}\n"
  },
  {
    "path": "feedback_test.go",
    "content": "package apns_test\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"encoding/hex\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"time\"\n\t. \"github.com/onsi/ginkgo\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/timehop/apns\"\n)\n\nvar _ = Describe(\"Feedback\", func() {\n\tDescribe(\".NewFeedback\", func() {\n\t\tContext(\"bad cert/key pair\", func() {\n\t\t\tIt(\"should error out\", func() {\n\t\t\t\t_, err := apns.NewFeedback(apns.ProductionGateway, \"missing\", \"missing_also\")\n\t\t\t\tExpect(err).NotTo(BeNil())\n\t\t\t})\n\t\t})\n\n\t\tContext(\"valid cert/key pair\", func() {\n\t\t\tIt(\"should create a valid client\", func() {\n\t\t\t\t_, err := apns.NewFeedback(apns.ProductionGateway, DummyCert, DummyKey)\n\t\t\t\tExpect(err).To(BeNil())\n\t\t\t})\n\t\t})\n\t})\n\n\tDescribe(\".NewFeedbackWithFiles\", func() {\n\t\tContext(\"missing cert/key pair\", func() {\n\t\t\tIt(\"should error out\", func() {\n\t\t\t\t_, err := apns.NewFeedbackWithFiles(apns.ProductionGateway, \"missing\", \"missing_also\")\n\t\t\t\tExpect(err).NotTo(BeNil())\n\t\t\t})\n\t\t})\n\n\t\tContext(\"valid cert/key pair\", func() {\n\t\t\tvar certFile, keyFile *os.File\n\n\t\t\tBeforeEach(func() {\n\t\t\t\tcertFile, _ = ioutil.TempFile(\"\", \"cert.pem\")\n\t\t\t\tcertFile.Write([]byte(DummyCert))\n\t\t\t\tcertFile.Close()\n\n\t\t\t\tkeyFile, _ = ioutil.TempFile(\"\", \"key.pem\")\n\t\t\t\tkeyFile.Write([]byte(DummyKey))\n\t\t\t\tkeyFile.Close()\n\t\t\t})\n\n\t\t\tAfterEach(func() {\n\t\t\t\tif certFile != nil {\n\t\t\t\t\tos.Remove(certFile.Name())\n\t\t\t\t}\n\n\t\t\t\tif keyFile != nil {\n\t\t\t\t\tos.Remove(keyFile.Name())\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tIt(\"should create a valid client\", func() {\n\t\t\t\t_, err := apns.NewFeedbackWithFiles(apns.ProductionGateway, certFile.Name(), keyFile.Name())\n\t\t\t\tExpect(err).To(BeNil())\n\t\t\t})\n\t\t})\n\t})\n\n\tDescribe(\"#Receive\", func() {\n\t\tContext(\"could not connect\", func() {\n\t\t\tIt(\"should not receive anything\", func() {\n\t\t\t\ts := &mockTLSServer{}\n\n\t\t\t\tf, _ := apns.NewFeedback(s.Address(), DummyCert, DummyKey)\n\t\t\t\tf.Conn.Conf.InsecureSkipVerify = true\n\n\t\t\t\tc := f.Receive()\n\n\t\t\t\tr := 0\n\t\t\t\tfor _ = range c {\n\t\t\t\t\tr += 1\n\t\t\t\t}\n\n\t\t\t\tExpect(r).To(Equal(0))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"times out\", func() {\n\t\t\tas := [][]serverAction{\n\t\t\t\t[]serverAction{\n\t\t\t\t\tserverAction{action: readAction, data: []byte{}},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\twithMockServer(as, func(s *mockTLSServer) {\n\t\t\t\tf, _ := apns.NewFeedback(s.Address(), DummyCert, DummyKey)\n\t\t\t\tf.Conn.Conf.InsecureSkipVerify = true\n\n\t\t\t\tIt(\"should not receive anything\", func() {\n\t\t\t\t\tc := f.Receive()\n\n\t\t\t\t\tr := 0\n\t\t\t\t\tfor _ = range c {\n\t\t\t\t\t\tr += 1\n\t\t\t\t\t}\n\n\t\t\t\t\tExpect(r).To(Equal(0))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tContext(\"with feedback\", func() {\n\t\t\tf1 := bytes.NewBuffer([]byte{})\n\t\t\tf2 := bytes.NewBuffer([]byte{})\n\t\t\tf3 := bytes.NewBuffer([]byte{})\n\n\t\t\t// The final token strings\n\t\t\tt1 := \"00a18269661e9406aea59a5620b05c7c0e371574fa6f251951de8d7a5a292535\"\n\t\t\tt2 := \"00a1a4b7294fcfbc5293f63d4298fcecd9c20a893befd45adceead5fc92d3319\"\n\t\t\tt3 := \"00a1b7893d5e85eb8bb7bf0846b464d075248555118ae893b06e96cfb8d678e3\"\n\n\t\t\tbt1, _ := hex.DecodeString(t1)\n\t\t\tbt2, _ := hex.DecodeString(t2)\n\t\t\tbt3, _ := hex.DecodeString(t3)\n\n\t\t\tbinary.Write(f1, binary.BigEndian, uint32(1404358249))\n\t\t\tbinary.Write(f1, binary.BigEndian, uint16(len(bt1)))\n\t\t\tbinary.Write(f1, binary.BigEndian, bt1)\n\n\t\t\tbinary.Write(f2, binary.BigEndian, uint32(1404352249))\n\t\t\tbinary.Write(f2, binary.BigEndian, uint16(len(bt2)))\n\t\t\tbinary.Write(f2, binary.BigEndian, bt2)\n\n\t\t\tbinary.Write(f3, binary.BigEndian, uint32(1394352249))\n\t\t\tbinary.Write(f3, binary.BigEndian, uint16(len(bt3)))\n\t\t\tbinary.Write(f3, binary.BigEndian, bt3)\n\n\t\t\tas := [][]serverAction{\n\t\t\t\t[]serverAction{\n\t\t\t\t\tserverAction{action: writeAction, data: f1.Bytes()},\n\t\t\t\t\tserverAction{action: writeAction, data: f2.Bytes()},\n\t\t\t\t\tserverAction{action: writeAction, data: f3.Bytes()},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tIt(\"should receive feedback\", func(d Done) {\n\t\t\t\twithMockServer(as, func(s *mockTLSServer) {\n\t\t\t\t\tf, _ := apns.NewFeedback(s.Address(), DummyCert, DummyKey)\n\t\t\t\t\tf.Conn.Conf.InsecureSkipVerify = true\n\n\t\t\t\t\tc := f.Receive()\n\n\t\t\t\t\tr1 := <-c\n\t\t\t\t\tExpect(r1.Timestamp).To(Equal(time.Unix(1404358249, 0)))\n\t\t\t\t\tExpect(r1.TokenLength).To(Equal(uint16(len(bt1))))\n\t\t\t\t\tExpect(r1.DeviceToken).To(Equal(t1))\n\n\t\t\t\t\tr2 := <-c\n\t\t\t\t\tExpect(r2.Timestamp).To(Equal(time.Unix(1404352249, 0)))\n\t\t\t\t\tExpect(r2.TokenLength).To(Equal(uint16(len(bt2))))\n\t\t\t\t\tExpect(r2.DeviceToken).To(Equal(t2))\n\n\t\t\t\t\tr3 := <-c\n\t\t\t\t\tExpect(r3.Timestamp).To(Equal(time.Unix(1394352249, 0)))\n\t\t\t\t\tExpect(r3.TokenLength).To(Equal(uint16(len(bt3))))\n\t\t\t\t\tExpect(r3.DeviceToken).To(Equal(t3))\n\n\t\t\t\t\t<-c\n\t\t\t\t\tclose(d)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/timehop/apns\n\ngo 1.20\n\nrequire (\n\tgithub.com/onsi/ginkgo v1.16.5\n\tgithub.com/onsi/gomega v1.27.6\n)\n\nrequire (\n\tgithub.com/fsnotify/fsnotify v1.4.9 // indirect\n\tgithub.com/google/go-cmp v0.5.9 // indirect\n\tgithub.com/nxadm/tail v1.4.8 // indirect\n\tgolang.org/x/net v0.8.0 // indirect\n\tgolang.org/x/sys v0.6.0 // indirect\n\tgolang.org/x/text v0.8.0 // indirect\n\tgopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=\ngithub.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=\ngithub.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=\ngithub.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=\ngithub.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=\ngithub.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=\ngithub.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=\ngithub.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=\ngithub.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=\ngithub.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=\ngithub.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=\ngithub.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=\ngithub.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=\ngithub.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=\ngithub.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=\ngithub.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=\ngithub.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=\ngolang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=\ngolang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "notification.go",
    "content": "package apns\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n)\n\nconst (\n\tPriorityImmediate     = 10\n\tPriorityPowerConserve = 5\n)\n\nconst (\n\tcommandID = 2\n\n\t// Items IDs\n\tdeviceTokenItemID            = 1\n\tpayloadItemID                = 2\n\tnotificationIdentifierItemID = 3\n\texpirationDateItemID         = 4\n\tpriorityItemID               = 5\n\n\t// Item lengths\n\tdeviceTokenItemLength            = 32\n\tnotificationIdentifierItemLength = 4\n\texpirationDateItemLength         = 4\n\tpriorityItemLength               = 1\n)\n\ntype NotificationResult struct {\n\tNotif Notification\n\tErr   Error\n}\n\ntype Alert struct {\n\t// Do not add fields without updating the implementation of isZero.\n\tBody         string   `json:\"body,omitempty\"`\n\tTitle        string   `json:\"title,omitempty\"`\n\tAction       string   `json:\"action,omitempty\"`\n\tLocKey       string   `json:\"loc-key,omitempty\"`\n\tLocArgs      []string `json:\"loc-args,omitempty\"`\n\tActionLocKey string   `json:\"action-loc-key,omitempty\"`\n\tLaunchImage  string   `json:\"launch-image,omitempty\"`\n}\n\nfunc (a *Alert) isSimple() bool {\n\treturn len(a.Title) == 0 && len(a.Action) == 0 && len(a.LocKey) == 0 && len(a.LocArgs) == 0 && len(a.ActionLocKey) == 0 && len(a.LaunchImage) == 0\n}\n\nfunc (a *Alert) isZero() bool {\n\treturn a.isSimple() && len(a.Body) == 0\n}\n\ntype APS struct {\n\tAlert            Alert\n\tBadge            BadgeNumber\n\tSound            string\n\tContentAvailable int\n\tURLArgs          []string\n\tCategory         string // requires iOS 8+\n\tAccountId        string // for email push notifications\n}\n\nfunc (aps APS) MarshalJSON() ([]byte, error) {\n\tdata := make(map[string]interface{})\n\n\tif !aps.Alert.isZero() {\n\t\tif aps.Alert.isSimple() {\n\t\t\tdata[\"alert\"] = aps.Alert.Body\n\t\t} else {\n\t\t\tdata[\"alert\"] = aps.Alert\n\t\t}\n\t}\n\tif aps.Badge.IsSet {\n\t\tdata[\"badge\"] = aps.Badge\n\t}\n\tif aps.Sound != \"\" {\n\t\tdata[\"sound\"] = aps.Sound\n\t}\n\tif aps.ContentAvailable != 0 {\n\t\tdata[\"content-available\"] = aps.ContentAvailable\n\t}\n\tif aps.Category != \"\" {\n\t\tdata[\"category\"] = aps.Category\n\t}\n\tif aps.URLArgs != nil && len(aps.URLArgs) != 0 {\n\t\tdata[\"url-args\"] = aps.URLArgs\n\t}\n\tif aps.AccountId != \"\" {\n\t\tdata[\"account-id\"] = aps.AccountId\n\t}\n\n\treturn json.Marshal(data)\n}\n\ntype Payload struct {\n\tAPS APS\n\t// MDM for mobile device management\n\tMDM          string\n\tcustomValues map[string]interface{}\n}\n\nfunc (p *Payload) MarshalJSON() ([]byte, error) {\n\tif len(p.MDM) != 0 {\n\t\tp.customValues[\"mdm\"] = p.MDM\n\t} else {\n\t\tp.customValues[\"aps\"] = p.APS\n\t}\n\n\treturn json.Marshal(p.customValues)\n}\n\nfunc (p *Payload) SetCustomValue(key string, value interface{}) error {\n\tif key == \"aps\" {\n\t\treturn errors.New(\"cannot assign a custom APS value in payload\")\n\t}\n\n\tp.customValues[key] = value\n\n\treturn nil\n}\n\ntype Notification struct {\n\tID          string\n\tDeviceToken string\n\tIdentifier  uint32\n\tExpiration  *time.Time\n\tPriority    int\n\tPayload     *Payload\n}\n\nfunc NewNotification() Notification {\n\treturn Notification{Payload: NewPayload()}\n}\n\nfunc NewPayload() *Payload {\n\treturn &Payload{customValues: map[string]interface{}{}}\n}\n\nfunc (n Notification) ToBinary() ([]byte, error) {\n\tb := []byte{}\n\n\tbinTok, err := hex.DecodeString(n.DeviceToken)\n\tif err != nil {\n\t\treturn b, fmt.Errorf(\"convert token to hex error: %s\", err)\n\t}\n\n\tj, _ := json.Marshal(n.Payload)\n\n\tbuf := bytes.NewBuffer(b)\n\n\t// Token\n\tbinary.Write(buf, binary.BigEndian, uint8(deviceTokenItemID))\n\tbinary.Write(buf, binary.BigEndian, uint16(deviceTokenItemLength))\n\tbinary.Write(buf, binary.BigEndian, binTok)\n\n\t// Payload\n\tbinary.Write(buf, binary.BigEndian, uint8(payloadItemID))\n\tbinary.Write(buf, binary.BigEndian, uint16(len(j)))\n\tbinary.Write(buf, binary.BigEndian, j)\n\n\t// Identifier\n\tbinary.Write(buf, binary.BigEndian, uint8(notificationIdentifierItemID))\n\tbinary.Write(buf, binary.BigEndian, uint16(notificationIdentifierItemLength))\n\tbinary.Write(buf, binary.BigEndian, uint32(n.Identifier))\n\n\t// Expiry\n\tbinary.Write(buf, binary.BigEndian, uint8(expirationDateItemID))\n\tbinary.Write(buf, binary.BigEndian, uint16(expirationDateItemLength))\n\tif n.Expiration == nil {\n\t\tbinary.Write(buf, binary.BigEndian, uint32(0))\n\t} else {\n\t\tbinary.Write(buf, binary.BigEndian, uint32(n.Expiration.Unix()))\n\t}\n\n\t// Priority\n\tbinary.Write(buf, binary.BigEndian, uint8(priorityItemID))\n\tbinary.Write(buf, binary.BigEndian, uint16(priorityItemLength))\n\tbinary.Write(buf, binary.BigEndian, uint8(n.Priority))\n\n\tframebuf := bytes.NewBuffer([]byte{})\n\tbinary.Write(framebuf, binary.BigEndian, uint8(commandID))\n\tbinary.Write(framebuf, binary.BigEndian, uint32(buf.Len()))\n\tbinary.Write(framebuf, binary.BigEndian, buf.Bytes())\n\n\treturn framebuf.Bytes(), nil\n}\n"
  },
  {
    "path": "notification_test.go",
    "content": "package apns_test\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"encoding/json\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/timehop/apns\"\n)\n\nvar _ = Describe(\"Notifications\", func() {\n\tDescribe(\"Alert\", func() {\n\t\tDescribe(\"JSON marshalling\", func() {\n\t\t\tContext(\"only body\", func() {\n\t\t\t\tIt(\"should just have that field\", func() {\n\t\t\t\t\ta := apns.Alert{Body: \"whatup\"}\n\n\t\t\t\t\tj, err := json.Marshal(a)\n\n\t\t\t\t\tExpect(err).To(BeNil())\n\t\t\t\t\tExpect(j).To(Equal([]byte(`{\"body\":\"whatup\"}`)))\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tContext(\"only loc-key\", func() {\n\t\t\t\tIt(\"should just have that field\", func() {\n\t\t\t\t\ta := apns.Alert{LocKey: \"localization\"}\n\n\t\t\t\t\tj, err := json.Marshal(a)\n\n\t\t\t\t\tExpect(err).To(BeNil())\n\t\t\t\t\tExpect(j).To(Equal([]byte(`{\"loc-key\":\"localization\"}`)))\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tContext(\"only loc-args\", func() {\n\t\t\t\tIt(\"should just have that field\", func() {\n\t\t\t\t\ta := apns.Alert{LocArgs: []string{\"world\", \"cup\"}}\n\n\t\t\t\t\tj, err := json.Marshal(a)\n\n\t\t\t\t\tExpect(err).To(BeNil())\n\t\t\t\t\tExpect(j).To(Equal([]byte(`{\"loc-args\":[\"world\",\"cup\"]}`)))\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tContext(\"only action-loc-key\", func() {\n\t\t\t\tIt(\"should just have that field\", func() {\n\t\t\t\t\ta := apns.Alert{ActionLocKey: \"akshun localization\"}\n\n\t\t\t\t\tj, err := json.Marshal(a)\n\n\t\t\t\t\tExpect(err).To(BeNil())\n\t\t\t\t\tExpect(j).To(Equal([]byte(`{\"action-loc-key\":\"akshun localization\"}`)))\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tContext(\"only launch image\", func() {\n\t\t\t\tIt(\"should just have that field\", func() {\n\t\t\t\t\ta := apns.Alert{LaunchImage: \"dee fault\"}\n\n\t\t\t\t\tj, err := json.Marshal(a)\n\n\t\t\t\t\tExpect(err).To(BeNil())\n\t\t\t\t\tExpect(j).To(Equal([]byte(`{\"launch-image\":\"dee fault\"}`)))\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tContext(\"fully loaded\", func() {\n\t\t\t\tIt(\"should serialize\", func() {\n\t\t\t\t\ta := apns.Alert{Body: \"USA scores!\", LocKey: \"game\", LocArgs: []string{\"USA\", \"BRA\"}, LaunchImage: \"scoreboard\"}\n\n\t\t\t\t\tj, err := json.Marshal(a)\n\n\t\t\t\t\tExpect(err).To(BeNil())\n\t\t\t\t\tExpect(j).To(Equal([]byte(`{\"body\":\"USA scores!\",\"loc-key\":\"game\",\"loc-args\":[\"USA\",\"BRA\"],\"launch-image\":\"scoreboard\"}`)))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t})\n\n\tDescribe(\"Safari\", func() {\n\t\tDescribe(\"#MarshalJSON\", func() {\n\t\t\tContext(\"with complete payload\", func() {\n\t\t\t\tIt(\"should marshal APS\", func() {\n\t\t\t\t\tp := apns.NewPayload()\n\n\t\t\t\t\tp.APS.Alert.Title = \"Hello World!\"\n\t\t\t\t\tp.APS.Alert.Body = \"This is a body\"\n\t\t\t\t\tp.APS.Alert.Action = \"Launch\"\n\t\t\t\t\tp.APS.URLArgs = []string{\"hello\", \"world\"}\n\n\t\t\t\t\tb, err := json.Marshal(p)\n\n\t\t\t\t\tExpect(err).To(BeNil())\n\t\t\t\t\tExpect(b).To(Equal([]byte(`{\"aps\":{\"alert\":{\"body\":\"This is a body\",\"title\":\"Hello World!\",\"action\":\"Launch\"},\"url-args\":[\"hello\",\"world\"]}}`)))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t})\n\n\tDescribe(\"Payload\", func() {\n\t\tDescribe(\"#MarshalJSON\", func() {\n\t\t\tContext(\"no alert (as with Passbook)\", func() {\n\t\t\t\tIt(\"should not contain the alert struct\", func() {\n\t\t\t\t\tp := apns.NewPayload()\n\t\t\t\t\tb, err := json.Marshal(p)\n\n\t\t\t\t\tExpect(err).To(BeNil())\n\t\t\t\t\tExpect(b).To(Equal([]byte(`{\"aps\":{}}`)))\n\t\t\t\t})\n\t\t\t})\n\t\t\tContext(\"no alert with content available (as with Newsstand)\", func() {\n\t\t\t\tIt(\"should not contain the alert struct\", func() {\n\t\t\t\t\tp := apns.NewPayload()\n\t\t\t\t\tp.APS.ContentAvailable = 1\n\t\t\t\t\tb, err := json.Marshal(p)\n\n\t\t\t\t\tExpect(err).To(BeNil())\n\t\t\t\t\tExpect(b).To(Equal([]byte(`{\"aps\":{\"content-available\":1}}`)))\n\t\t\t\t})\n\t\t\t})\n\t\t\tContext(\"with only APS\", func() {\n\t\t\t\tIt(\"should marshal APS\", func() {\n\t\t\t\t\tp := apns.NewPayload()\n\n\t\t\t\t\tp.APS.Alert.Body = \"testing\"\n\n\t\t\t\t\tb, err := json.Marshal(p)\n\n\t\t\t\t\tExpect(err).To(BeNil())\n\t\t\t\t\tExpect(b).To(Equal([]byte(`{\"aps\":{\"alert\":\"testing\"}}`)))\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tContext(\"with custom attributes APS\", func() {\n\t\t\t\tIt(\"should marshal APS\", func() {\n\t\t\t\t\tp := apns.NewPayload()\n\n\t\t\t\t\tp.APS.Alert.Body = \"testing\"\n\t\t\t\t\tp.SetCustomValue(\"email\", \"come@me.bro\")\n\n\t\t\t\t\tb, err := json.Marshal(p)\n\n\t\t\t\t\tExpect(err).To(BeNil())\n\t\t\t\t\tExpect(b).To(Equal([]byte(`{\"aps\":{\"alert\":\"testing\"},\"email\":\"come@me.bro\"}`)))\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tContext(\"with only MDM\", func() {\n\t\t\t\tIt(\"should marshal MDM\", func() {\n\t\t\t\t\tp := apns.NewPayload()\n\n\t\t\t\t\tp.MDM = \"00000000-1111-3333-4444-555555555555\"\n\n\t\t\t\t\tb, err := json.Marshal(p)\n\n\t\t\t\t\tExpect(err).To(BeNil())\n\t\t\t\t\tExpect(b).To(Equal([]byte(`{\"mdm\":\"00000000-1111-3333-4444-555555555555\"}`)))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t})\n\n\tDescribe(\"APS\", func() {\n\t\tContext(\"badge with a zero (clears notifications)\", func() {\n\t\t\tIt(\"should contain zero\", func() {\n\t\t\t\ta := apns.APS{}\n\t\t\t\ta.Badge.Set(0)\n\n\t\t\t\tj, err := json.Marshal(a)\n\n\t\t\t\tExpect(err).To(BeNil())\n\t\t\t\tExpect(j).To(Equal([]byte(`{\"badge\":0}`)))\n\t\t\t})\n\t\t})\n\t\tContext(\"no badge specified (do nothing)\", func() {\n\t\t\tIt(\"should omit the badge field\", func() {\n\t\t\t\ta := apns.APS{}\n\n\t\t\t\tj, err := json.Marshal(a)\n\n\t\t\t\tExpect(err).To(BeNil())\n\t\t\t\tExpect(j).To(Equal([]byte(`{}`)))\n\t\t\t})\n\t\t})\n\t\tContext(\"email account id\", func() {\n\t\t\tIt(\"should contain the account id\", func() {\n\t\t\t\ta := apns.APS{AccountId: \"1234\"}\n\n\t\t\t\tj, err := json.Marshal(a)\n\n\t\t\t\tExpect(err).To(BeNil())\n\t\t\t\tExpect(j).To(Equal([]byte(`{\"account-id\":\"1234\"}`)))\n\t\t\t})\n\t\t})\n\t})\n\n\tDescribe(\"Notification\", func() {\n\t\tDescribe(\"#ToBinary\", func() {\n\t\t\tContext(\"invalid token format\", func() {\n\t\t\t\tn := apns.NewNotification()\n\t\t\t\tn.DeviceToken = \"totally not a valid token\"\n\n\t\t\t\tIt(\"should return an error\", func() {\n\t\t\t\t\t_, err := n.ToBinary()\n\t\t\t\t\tExpect(err).NotTo(BeNil())\n\t\t\t\t\tExpect(err.Error()).To(ContainSubstring(\"convert token to hex error\"))\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tContext(\"valid payload\", func() {\n\t\t\t\tIt(\"should generate the correct byte payload with expiry\", func() {\n\t\t\t\t\tt := time.Unix(1404102833, 0)\n\n\t\t\t\t\tn := apns.NewNotification()\n\t\t\t\t\tn.Identifier = uint32(123123)\n\t\t\t\t\tn.DeviceToken = \"9999999999999999999999999999999999999999999999999999999999999999\"\n\t\t\t\t\tn.Priority = apns.PriorityImmediate\n\t\t\t\t\tn.Expiration = &t\n\n\t\t\t\t\tb, err := n.ToBinary()\n\n\t\t\t\t\tExpect(err).To(BeNil())\n\n\t\t\t\t\tbuf := bytes.NewBuffer(b)\n\n\t\t\t\t\tvar expiry uint32\n\n\t\t\t\t\tbuf.Next(1 + 4 + 1 + 2 + 32 + 1 + 2 + 20 + 1 + 2 + 4 + 1 + 2)\n\n\t\t\t\t\t// Expiry\n\t\t\t\t\tbinary.Read(buf, binary.BigEndian, &expiry)\n\t\t\t\t})\n\n\t\t\t\tIt(\"should generate the correct byte payload\", func() {\n\t\t\t\t\tn := apns.NewNotification()\n\t\t\t\t\tn.Identifier = uint32(123123)\n\t\t\t\t\tn.DeviceToken = \"9999999999999999999999999999999999999999999999999999999999999999\"\n\t\t\t\t\tn.Priority = apns.PriorityImmediate\n\t\t\t\t\tb, err := n.ToBinary()\n\n\t\t\t\t\tExpect(err).To(BeNil())\n\n\t\t\t\t\tbuf := bytes.NewBuffer(b)\n\n\t\t\t\t\tvar command, tokID, payloadID, identifierID, expiryID, priorityID uint8\n\t\t\t\t\tvar tokLen, payloadLen, identifierLen, expiryLen, priorityLen uint16\n\t\t\t\t\tvar frameLen, identifier, expiry uint32\n\t\t\t\t\tvar priority byte\n\t\t\t\t\tvar tok [32]byte\n\t\t\t\t\tvar payload [10]byte\n\n\t\t\t\t\tbinary.Read(buf, binary.BigEndian, &command)\n\t\t\t\t\tbinary.Read(buf, binary.BigEndian, &frameLen)\n\n\t\t\t\t\t// Token\n\t\t\t\t\tbinary.Read(buf, binary.BigEndian, &tokID)\n\t\t\t\t\tbinary.Read(buf, binary.BigEndian, &tokLen)\n\t\t\t\t\tbinary.Read(buf, binary.BigEndian, &tok)\n\n\t\t\t\t\t// Payload\n\t\t\t\t\tbinary.Read(buf, binary.BigEndian, &payloadID)\n\t\t\t\t\tbinary.Read(buf, binary.BigEndian, &payloadLen)\n\t\t\t\t\tbinary.Read(buf, binary.BigEndian, &payload)\n\n\t\t\t\t\t// Identifier\n\t\t\t\t\tbinary.Read(buf, binary.BigEndian, &identifierID)\n\t\t\t\t\tbinary.Read(buf, binary.BigEndian, &identifierLen)\n\t\t\t\t\tbinary.Read(buf, binary.BigEndian, &identifier)\n\n\t\t\t\t\t// Expiry\n\t\t\t\t\tbinary.Read(buf, binary.BigEndian, &expiryID)\n\t\t\t\t\tbinary.Read(buf, binary.BigEndian, &expiryLen)\n\t\t\t\t\tbinary.Read(buf, binary.BigEndian, &expiry)\n\n\t\t\t\t\t// Priority\n\t\t\t\t\tbinary.Read(buf, binary.BigEndian, &priorityID)\n\t\t\t\t\tbinary.Read(buf, binary.BigEndian, &priorityLen)\n\t\t\t\t\tbinary.Read(buf, binary.BigEndian, &priority)\n\n\t\t\t\t\tExpect(command).To(Equal(uint8(2)))\n\t\t\t\t\tExpect(frameLen).To(Equal(uint32(66)))\n\n\t\t\t\t\t// Token\n\t\t\t\t\tExpect(tokID).To(Equal(uint8(1)))\n\t\t\t\t\tExpect(tokLen).To(Equal(uint16(32)))\n\t\t\t\t\tExpect(tok).To(Equal([32]byte{153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153}))\n\n\t\t\t\t\t// Payload\n\t\t\t\t\tExpect(payloadID).To(Equal(uint8(2)))\n\t\t\t\t\tExpect(payloadLen).To(Equal(uint16(10)))\n\t\t\t\t\tExpect(payload).To(Equal([10]byte{123, 34, 97, 112, 115, 34, 58, 123, 125, 125}))\n\n\t\t\t\t\t// Identifier\n\t\t\t\t\tExpect(identifierID).To(Equal(uint8(3)))\n\t\t\t\t\tExpect(identifierLen).To(Equal(uint16(4)))\n\t\t\t\t\tExpect(identifier).To(Equal(uint32(123123)))\n\n\t\t\t\t\t// Expiry\n\t\t\t\t\tExpect(expiryID).To(Equal(uint8(4)))\n\t\t\t\t\tExpect(expiryLen).To(Equal(uint16(4)))\n\t\t\t\t\tExpect(expiry).To(Equal(uint32(0)))\n\n\t\t\t\t\t// Priority\n\t\t\t\t\tExpect(priorityID).To(Equal(uint8(5)))\n\t\t\t\t\tExpect(priorityLen).To(Equal(uint16(1)))\n\t\t\t\t\tExpect(priority).To(Equal(uint8(10)))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t})\n})\n"
  }
]