Skip to content

Instantly share code, notes, and snippets.

@scottferg
Created August 29, 2012 16:10
Show Gist options
  • Save scottferg/3514962 to your computer and use it in GitHub Desktop.
Save scottferg/3514962 to your computer and use it in GitHub Desktop.
iOS Push notification service in Go
package main
import (
"bytes"
"crypto/tls"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"net"
"time"
)
type ApnNotification struct {
DeviceToken string
Payload map[string]string
}
func (n *ApnNotification) Connect() (*tls.Conn, error) {
cer, err := tls.LoadX509KeyPair("cert.pem", "key.pem")
if err != nil {
return nil, &PushError{Text: fmt.Sprintf("Error loading certificate: %s\n", err.Error())}
}
cfg := &tls.Config{
Certificates: []tls.Certificate{cer},
}
c, err := net.Dial("tcp", "gateway.sandbox.push.apple.com:2195")
if err != nil {
return nil, &PushError{Text: fmt.Sprintf("Error connecting: %s\n", err.Error())}
}
result := tls.Client(c, cfg)
err = result.Handshake()
if err != nil {
return nil, &PushError{Text: fmt.Sprintf("Error authenticating: %s\n", err.Error())}
}
return result, nil
}
func (n *ApnNotification) BuildMessage() []byte {
// prepare binary payload from JSON structure
p := make(map[string]interface{})
p["aps"] = n.Payload
bp, _ := json.Marshal(p)
// decode hexadecimal push device token to binary byte array
t, _ := hex.DecodeString(n.DeviceToken)
// build the actual message
buf := bytes.NewBuffer([]byte{})
binary.Write(buf, binary.BigEndian, uint8(1)) // Command byte (1)
binary.Write(buf, binary.BigEndian, uint32(1)) // Optional transaction ID
binary.Write(buf, binary.BigEndian, uint32(60*60*24*7)) // Expiration (1 week)
binary.Write(buf, binary.BigEndian, uint16(len(t))) // Device token length
binary.Write(buf, binary.BigEndian, t) // Device token
binary.Write(buf, binary.BigEndian, uint16(len(bp))) // Payload length
binary.Write(buf, binary.BigEndian, bp) // Payload
return buf.Bytes()
}
func (n *ApnNotification) ParseError(r []byte) error {
code, _ := binary.Uvarint(r)
switch code {
case 0x01:
return &PushError{Text: "Processing error"}
case 0x02:
return &PushError{Text: "Missing device token"}
case 0x03:
return &PushError{Text: "Missing topic"}
case 0x04:
return &PushError{Text: "Missing payload"}
case 0x05:
return &PushError{Text: "Invalid token size"}
case 0x06:
return &PushError{Text: "Invalid topic size"}
case 0x07:
return &PushError{Text: "Invalid payload size"}
case 0x08:
return &PushError{Text: "Invalid token"}
}
return nil
}
func (n *ApnNotification) Send() error {
c, err := n.Connect()
defer c.Close()
if err != nil {
return err
}
m := n.BuildMessage()
// Write the message on the connection
_, err = c.Write(m)
if err != nil {
return &PushError{Text: fmt.Sprintf("Error writing: %s\n", err.Error())}
}
// Get errors
d, _ := time.ParseDuration("5s")
c.SetReadDeadline(time.Now().Add(d))
res := [6]byte{}
r, err := c.Read(res[:])
if r > 0 {
return n.ParseError(res[1:2])
}
return nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment