diff --git a/pkg/bluetooth/bluetooth.go b/pkg/bluetooth/bluetooth.go index db1b6a2..b46aa24 100644 --- a/pkg/bluetooth/bluetooth.go +++ b/pkg/bluetooth/bluetooth.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "encoding/hex" "errors" + "fmt" "hash/crc32" "sync" "time" @@ -323,8 +324,11 @@ func (b *Ble) loop(stop chan bool, in *input) { case <-stop: return case msg := <-b.messageOutput: - if err := b.writeMessage(msg, in, stop); err != nil { + if err := b.writeMessage(msg, in, stop); err == errStopped { return + } else if err != nil { + // The phone gets no answer and asks again. + log.Errorf("pkg bluetooth; error writing message: %s", err) } case cmd := <-in.cmd: msg, err := b.readMessage(cmd, in, stop) @@ -373,8 +377,8 @@ func (b *Ble) expectCommand(expected Packet, in *input, stop chan bool) error { if err != nil { return err } - if !bytes.Equal(expected[:1], cmd[:1]) { - log.Fatalf("pkg bluetooth; expected command: %s. received command: %s", expected, cmd) + if len(cmd) == 0 || cmd[0] != expected[0] { + return fmt.Errorf("expected command: %x. received command: %x", expected, cmd) } return nil } @@ -467,8 +471,8 @@ func (b *Ble) readMessage(cmd Packet, in *input, stop chan bool) (*message.Messa var checksum []byte log.Trace("pkg bluetooth; Reading RTS") - if !bytes.Equal(CmdRTS[:1], cmd[:1]) { - log.Fatalf("pkg bluetooth; expected command: %x. received command: %x", CmdRTS, cmd) + if len(cmd) == 0 || cmd[0] != CmdRTS[0] { + return nil, fmt.Errorf("expected command: %x. received command: %x", CmdRTS, cmd) } log.Trace("pkg bluetooth; Sending CTS") diff --git a/pkg/pod/pod.go b/pkg/pod/pod.go index a32bced..a50d1c8 100644 --- a/pkg/pod/pod.go +++ b/pkg/pod/pod.go @@ -2,6 +2,7 @@ package pod import ( "encoding/json" + "fmt" "sync" "time" @@ -109,6 +110,14 @@ func (p *Pod) readSetup() (*message.Message, bool) { } } +// startOver hangs up on a phone whose message the pod can't use. The phone +// reconnects and sets up a new session. +func (p *Pod) startOver(format string, args ...interface{}) { + log.Errorf("pkg pod; "+format+"; hanging up", args...) + p.ble.ShutdownConnection() + go p.StartAcceptingCommands() +} + func (p *Pod) StartActivation() { pair := &pair.Pair{} @@ -117,14 +126,16 @@ func (p *Pod) StartActivation() { return } if err := pair.ParseSP1SP2(msg); err != nil { - log.Fatalf("pkg pod; error parsing SP1SP2 %s", err) + p.startOver("error parsing SP1SP2 %s", err) + return } // read PDM public key and nonce if msg, ok = p.readSetup(); !ok { return } if err := pair.ParseSPS1(msg); err != nil { - log.Fatalf("pkg pod; error parsing SPS1 %s", err) + p.startOver("error parsing SPS1 %s", err) + return } msg, err := pair.GenerateSPS1() @@ -138,7 +149,10 @@ func (p *Pod) StartActivation() { if msg, ok = p.readSetup(); !ok { return } - pair.ParseSPS2(msg) + if err := pair.ParseSPS2(msg); err != nil { + p.startOver("error parsing SPS2 %s", err) + return + } // send POD conf value msg, err = pair.GenerateSPS2() @@ -153,7 +167,8 @@ func (p *Pod) StartActivation() { } err = pair.ParseSP0GP0(msg) if err != nil { - log.Fatalf("pkg pod; could not parse SP0GP0: %s", err) + p.startOver("could not parse SP0GP0: %s", err) + return } // send P0 constant @@ -184,7 +199,8 @@ func (p *Pod) EapAka() { } err := session.ParseChallenge(msg) if err != nil { - log.Fatalf("pkg pod; error parsing the EAP-AKA challenge: %s", err) + p.startOver("error parsing the EAP-AKA challenge: %s", err) + return } msg, err = session.GenerateChallengeResponse() @@ -199,7 +215,8 @@ func (p *Pod) EapAka() { log.Debugf("pkg pod; success? %x", msg.Payload) // TODO: figure out how error looks like err = session.ParseSuccess(msg) if err != nil { - log.Fatalf("pkg pod; error parsing the EAP-AKA Success packet: %s", err) + p.startOver("error parsing the EAP-AKA Success packet: %s", err) + return } p.state.CK, p.state.NoncePrefix = session.CKNoncePrefix() @@ -272,17 +289,28 @@ func (p *Pod) CommandLoop(pMsg PodMsgBody) { decrypted, err := encrypt.DecryptMessage(p.state.CK, p.state.NoncePrefix, p.state.NonceSeq, msg) if err != nil { - log.Fatalf("pkg pod; could not decrypt message: %s", err) + p.mtx.Unlock() + p.startOver("could not decrypt message: %s", err) + return } p.state.NonceSeq++ + if len(decrypted.Payload) < 16 { + p.mtx.Unlock() + p.startOver("decrypted payload too short: %x", decrypted.Payload) + return + } cmd, err := command.Unmarshal(decrypted.Payload) if err != nil { - log.Fatalf("pkg pod; could not unmarshal command: %s", err) + p.mtx.Unlock() + p.startOver("could not unmarshal command: %s", err) + return } cmdSeq, requestID, err := cmd.GetHeaderData() if err != nil { - log.Fatalf("pkg pod; could not get command header data: %s", err) + p.mtx.Unlock() + p.startOver("could not get command header data: %s", err) + return } p.state.CmdSeq = cmdSeq @@ -290,9 +318,6 @@ func (p *Pod) CommandLoop(pMsg PodMsgBody) { data = decrypted.Payload n = len(data) log.Debugf("pkg pod; len = %d", n) - if n < 16 { - log.Fatalf("pkg pod; decrypted. Payload too short") - } pMsg.MsgBodyCommand = data[13 : n-5] if data[13] == 0x1c { pMsg.DeactivateFlag = true @@ -370,16 +395,19 @@ func (p *Pod) CommandLoop(pMsg PodMsgBody) { } // TODO check for SEQ numbers here and the Ack flag decrypted, err = encrypt.DecryptMessage(p.state.CK, p.state.NoncePrefix, p.state.NonceSeq, msg) - if err != nil { - log.Fatalf("pkg pod; could not decrypt message: %s", err) - } - p.state.NonceSeq++ - if len(decrypted.Payload) != 0 { - log.Fatalf("pkg pod; this should be empty message with ACK header %s", spew.Sdump(msg)) + if err == nil { + p.state.NonceSeq++ + if len(decrypted.Payload) != 0 { + err = fmt.Errorf("not an empty ACK: %s", spew.Sdump(msg)) + } } p.state.Save() p.takeSnapshot() p.mtx.Unlock() + if err != nil { + p.startOver("could not read the response ACK: %s", err) + return + } log.Debugf("notifyingStateChange") p.notifyStateChange() diff --git a/pkg/pod/pod_test.go b/pkg/pod/pod_test.go new file mode 100644 index 0000000..274033a --- /dev/null +++ b/pkg/pod/pod_test.go @@ -0,0 +1,85 @@ +package pod + +import ( + "path/filepath" + "sync" + "testing" + "time" + + "github.com/avereha/pod/pkg/bluetooth" + "github.com/avereha/pod/pkg/message" +) + +// radio carries one side's notifications to the other side, and records +// when the pod hangs up. +type radio struct { + mu sync.Mutex + peer *bluetooth.Ble + hungUp chan bool +} + +func (r *radio) to() *bluetooth.Ble { + r.mu.Lock() + defer r.mu.Unlock() + return r.peer +} + +func (r *radio) NotifyCmd(p bluetooth.Packet) error { + if b := r.to(); b != nil { + b.CmdWritten(p) + } + return nil +} + +func (r *radio) NotifyData(p bluetooth.Packet) error { + if b := r.to(); b != nil { + b.DataWritten(p) + } + return nil +} + +func (r *radio) Advertise([]byte) error { return nil } +func (r *radio) AdvertiseState(bluetooth.AdvertState) error { return nil } + +func (r *radio) Disconnect() { + select { + case r.hungUp <- true: + default: + } +} + +func newBle(t *testing.T, r *radio) *bluetooth.Ble { + b, err := bluetooth.New(func(*bluetooth.Ble) (bluetooth.Radio, error) { return r, nil }) + if err != nil { + t.Fatal(err) + } + return b +} + +// A session message the pod can't parse ends the connection, not the pod. +func TestAGarbledSessionMessageHangsUp(t *testing.T) { + toPhone, toPod := &radio{hungUp: make(chan bool, 1)}, &radio{} + podBle, phone := newBle(t, toPhone), newBle(t, toPod) + toPod.peer = podBle + toPhone.peer = phone + + state := NewFreshState(filepath.Join(t.TempDir(), "state.toml")) + state.LTK = make([]byte, 16) + p := New(podBle, state) + + podBle.Connected() + go p.StartAcceptingCommands() + podBle.CmdWritten(bluetooth.Packet{6, 1, 4}) + phone.Connected() + phone.StartMessageLoop() + + garbled := message.NewMessage(message.MessageTypeSessionEstablishment, []byte{1, 2, 3, 4}, []byte{5, 6, 7, 8}) + garbled.Payload = []byte{0x2b, 0x97, 0x1c, 0x90, 0xb9, 0xdc, 0x43, 0x9b} + phone.WriteMessage(garbled) + + select { + case <-toPhone.hungUp: + case <-time.After(2 * time.Second): + t.Fatal("the pod did not hang up") + } +}