Created
August 29, 2012 16:10
-
-
Save scottferg/3514962 to your computer and use it in GitHub Desktop.
iOS Push notification service in Go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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