in otr/otr.go [272:444]
func (c *Conversation) Receive(in []byte) (out []byte, encrypted bool, change SecurityChange, toSend [][]byte, err error) {
if bytes.HasPrefix(in, fragmentPrefix) {
in, err = c.processFragment(in)
if in == nil || err != nil {
return
}
}
if bytes.HasPrefix(in, msgPrefix) && in[len(in)-1] == '.' {
in = in[len(msgPrefix) : len(in)-1]
} else if version := isQuery(in); version > 0 {
c.authState = authStateAwaitingDHKey
c.reset()
toSend = c.encode(c.generateDHCommit())
return
} else {
// plaintext message
out = in
return
}
msg := make([]byte, base64.StdEncoding.DecodedLen(len(in)))
msgLen, err := base64.StdEncoding.Decode(msg, in)
if err != nil {
err = errors.New("otr: invalid base64 encoding in message")
return
}
msg = msg[:msgLen]
// The first two bytes are the protocol version (2)
if len(msg) < 3 || msg[0] != 0 || msg[1] != 2 {
err = errors.New("otr: invalid OTR message")
return
}
msgType := int(msg[2])
msg = msg[3:]
switch msgType {
case msgTypeDHCommit:
switch c.authState {
case authStateNone:
c.authState = authStateAwaitingRevealSig
if err = c.processDHCommit(msg); err != nil {
return
}
c.reset()
toSend = c.encode(c.generateDHKey())
return
case authStateAwaitingDHKey:
// This is a 'SYN-crossing'. The greater digest wins.
var cmp int
if cmp, err = c.compareToDHCommit(msg); err != nil {
return
}
if cmp > 0 {
// We win. Retransmit DH commit.
toSend = c.encode(c.serializeDHCommit())
return
} else {
// They win. We forget about our DH commit.
c.authState = authStateAwaitingRevealSig
if err = c.processDHCommit(msg); err != nil {
return
}
c.reset()
toSend = c.encode(c.generateDHKey())
return
}
case authStateAwaitingRevealSig:
if err = c.processDHCommit(msg); err != nil {
return
}
toSend = c.encode(c.serializeDHKey())
case authStateAwaitingSig:
if err = c.processDHCommit(msg); err != nil {
return
}
c.reset()
toSend = c.encode(c.generateDHKey())
c.authState = authStateAwaitingRevealSig
default:
panic("bad state")
}
case msgTypeDHKey:
switch c.authState {
case authStateAwaitingDHKey:
var isSame bool
if isSame, err = c.processDHKey(msg); err != nil {
return
}
if isSame {
err = errors.New("otr: unexpected duplicate DH key")
return
}
toSend = c.encode(c.generateRevealSig())
c.authState = authStateAwaitingSig
case authStateAwaitingSig:
var isSame bool
if isSame, err = c.processDHKey(msg); err != nil {
return
}
if isSame {
toSend = c.encode(c.serializeDHKey())
}
}
case msgTypeRevealSig:
if c.authState != authStateAwaitingRevealSig {
return
}
if err = c.processRevealSig(msg); err != nil {
return
}
toSend = c.encode(c.generateSig())
c.authState = authStateNone
c.state = stateEncrypted
change = NewKeys
case msgTypeSig:
if c.authState != authStateAwaitingSig {
return
}
if err = c.processSig(msg); err != nil {
return
}
c.authState = authStateNone
c.state = stateEncrypted
change = NewKeys
case msgTypeData:
if c.state != stateEncrypted {
err = errors.New("otr: encrypted message received without encrypted session established")
return
}
var tlvs []tlv
out, tlvs, err = c.processData(msg)
encrypted = true
EachTLV:
for _, inTLV := range tlvs {
switch inTLV.typ {
case tlvTypeDisconnected:
change = ConversationEnded
c.state = stateFinished
break EachTLV
case tlvTypeSMP1, tlvTypeSMP2, tlvTypeSMP3, tlvTypeSMP4, tlvTypeSMPAbort, tlvTypeSMP1WithQuestion:
var reply tlv
var complete bool
reply, complete, err = c.processSMP(inTLV)
if err == smpSecretMissingError {
err = nil
change = SMPSecretNeeded
c.smp.saved = &inTLV
return
}
if err == smpFailureError {
err = nil
change = SMPFailed
} else if complete {
change = SMPComplete
}
if reply.typ != 0 {
toSend = c.encode(c.generateData(nil, &reply))
}
break EachTLV
default:
// skip unknown TLVs
}
}
default:
err = errors.New("otr: unknown message type " + strconv.Itoa(msgType))
}
return
}