//- ----------------------------------------------------------------------------------------------------------------------- // AskSin++ // 2016-10-31 papa Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ // 2018-09-03 jp112sdl Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ //- ----------------------------------------------------------------------------------------------------------------------- #ifndef __DEVICE_H__ #define __DEVICE_H__ #include "AskSinPP.h" #include "Sign.h" #include "HMID.h" #include "Channel.h" #include "ChannelList.h" #include "Message.h" #include "Radio.h" #include "Led.h" #include "Activity.h" #define OTA_CONFIG_START 0x7fe0 // start address of 16 byte config data in bootloader #define OTA_MODEL_START 0x7ff0 // start address of 2 byte model id in bootloader #define OTA_SERIAL_START 0x7ff2 // start address of 10 byte serial number in bootloader #define OTA_HMID_START 0x7ffc // start address of 3 byte device id in bootloader namespace as { struct DeviceInfo { uint8_t DeviceID[3]; char Serial[11]; uint8_t DeviceModel[2]; uint8_t Firmware; uint8_t DeviceType; uint8_t DeviceInfo[2]; }; template class Device { public: typedef typename HalType::LedType LedType; typedef typename HalType::BatteryType BatteryType; typedef typename HalType::RadioType RadioType; private: HalType* hal; List0Type& list0; uint8_t msgcount; HMID lastdev; uint8_t lastmsg; #ifdef USE_HW_SERIAL uint8_t device_id[3] = { 0x00, 0x00, 0x00 }; #endif protected: Message msg; KeyStore kstore; const DeviceInfo& info; uint8_t numChannels; public: Device (const DeviceInfo& i, uint16_t addr, List0Type& l, uint8_t ch) : hal(0), list0(l), msgcount(0), lastmsg(0), kstore(addr), info(i), numChannels(ch) {} virtual ~Device () {} LedType& led () { return hal->led; } BatteryType& battery () { return hal->battery; } const BatteryType& battery () const { return hal->battery; } RadioType& radio () { return hal->radio; } KeyStore& keystore () { return this->kstore; } Activity& activity () { return hal->activity; } Message& message () { return msg; } void channels (uint8_t num) { numChannels = num; } uint8_t channels () const { return numChannels; } bool hasChannel (uint8_t number) const { return number != 0 && number <= channels(); } bool isRepeat(const Message& m) { if ( m.isRepeated() && lastdev == m.from() && lastmsg == m.count() ) { return true; } // store last message data lastdev = m.from(); lastmsg = m.count(); return false; } void setHal (HalType& h) { hal = &h; } uint8_t getConfigByte (uint8_t offset) { uint8_t data = 0; #ifdef USE_OTA_BOOTLOADER if ( offset < 16 ) { HalType::pgm_read(&data, OTA_CONFIG_START + offset, 1); } #elif defined(DEVICE_CONFIG) uint8_t tmp[] = {DEVICE_CONFIG}; if ( offset < sizeof(tmp) ) { data = tmp[offset]; } #endif return data; } void getDeviceID (HMID& id) { uint8_t ids[3]; memcpy_P(ids, info.DeviceID, 3); id = HMID(ids); } void getDeviceSerial (uint8_t* serial) { memcpy_P(serial, info.Serial, 10); } bool isDeviceSerial (const uint8_t* serial) { uint8_t tmp[10]; getDeviceSerial(tmp); return memcmp(serial, tmp, 10) == 0; } bool isDeviceID(const HMID& id) { HMID me; getDeviceID(me); return id == me; } void getDeviceModel (uint8_t* model) { #ifdef USE_OTA_BOOTLOADER HalType::pgm_read(model, OTA_MODEL_START, 2); #else memcpy_P(model, info.DeviceModel, sizeof(info.DeviceModel)); #endif } void getDeviceInfo (uint8_t* di) { // first byte is number of channels *di = this->channels(); memcpy_P(di + 1, info.DeviceInfo, sizeof(info.DeviceInfo)); } HMID getMasterID () { return list0.masterid(); } const List0Type& getList0 () { return list0; } bool pollRadio () { uint8_t num = radio().read(msg); // minimal msg is 10 byte // ignore own messages from radio if ( num >= 10 && isDeviceID(msg.from()) == false ) { return process(msg); } return false; } uint8_t nextcount () { return ++msgcount; } virtual void configChanged () {} virtual bool process(__attribute__((unused)) Message& msg) { return false; } bool isBroadcastMsg(Message msg) { return (msg.to() == HMID::broadcast && msg.isPairSerial()) || ( msg.isBroadcast() && (msg.isRemoteEvent() || msg.isSensorEvent()) ); } bool sendFake(Message& msg, const HMID& from) { msg.to(getMasterID()); if ( msg.to().valid() == true ) { msg.from(from); msg.setRpten(); // has to be set always return send(msg); } else { DPRINTLN("NO VALID RECEIVER (Central) DEFINED"); return true; } } bool send(Message& msg, const HMID& to) { msg.to(to); getDeviceID(msg.from()); msg.setRpten(); // has to be set always return send(msg); } bool send(Message& msg) { bool result = false; uint8_t maxsend = list0.transmitDevTryMax(); led().set(LedStates::send); while ( result == false && maxsend > 0 ) { result = radio().write(msg, msg.burstRequired()); DPRINT(F("<- ")); msg.dump(); maxsend--; if ( result == true && msg.ackRequired() == true && msg.to().valid() == true ) { Message response; if ( (result = waitResponse(msg, response, 60)) ) { // 600ms #ifdef USE_AES if ( response.isChallengeAes() == true ) { AesChallengeMsg& cm = response.aesChallenge(); result = processChallenge(msg, cm.challenge(), cm.keyindex()); } else #endif { result = response.isAck(); // if we got a Nack - we stop trying to send again if ( response.isNack() ) { maxsend = 0; } // we request wakeup // we got the flag to stay awake if ( msg.isWakeMeUp() || response.isKeepAwake() ) { activity().stayAwake(millis2ticks(500)); } } } DPRINT(F("waitAck: ")); DHEX((uint8_t)result); DPRINTLN(F("")); } } if ( result == true ) led().set(LedStates::ack); else led().set(LedStates::nack); return result; } void sendAck (Message& msg, uint8_t flag = 0x00) { msg.ack().init(flag); kstore.addAuth(msg); send(msg, msg.from()); } void sendAck2 (Message& msg, uint8_t flag = 0x00) { msg.ack2().init(flag); kstore.addAuth(msg); send(msg, msg.from()); } void sendNack (Message& msg) { msg.nack().init(); send(msg, msg.from()); } template void sendAck (Message& msg, ChannelType& ch, HMID id ) { msg.ackStatus().init(ch, radio().rssi()); ch.patchStatus(msg); kstore.addAuth(msg); sendFake(msg, id); ch.changed(false); } void sendDeviceInfo () { sendDeviceInfo(getMasterID(), nextcount()); } void sendDeviceInfo (const HMID& to, uint8_t count) { DeviceInfoMsg& pm = msg.deviceInfo(); pm.init(to, count); pm.fill(pgm_read_byte(&info.Firmware), pgm_read_byte(&info.DeviceType)); getDeviceModel(pm.model()); getDeviceSerial(pm.serial()); getDeviceInfo(pm.info()); send(msg, to); } void sendSerialInfo (const HMID& to, uint8_t count) { SerialInfoMsg& pm = msg.serialInfo(); pm.init(to, count); getDeviceSerial(pm.serial()); send(msg, to); } template void sendInfoActuatorStatus (const HMID& to, uint8_t count, ChannelType& ch, bool ack = true) { InfoActuatorStatusMsg& pm = msg.infoActuatorStatus(); pm.init(count, ch, radio().rssi()); if ( ack == false ) { pm.clearAck(); } ch.patchStatus(msg); send(msg, to); ch.changed(false); } template void sendFakeInfoActuatorStatus (const HMID& from, uint8_t count, ChannelType& ch, bool ack = true) { InfoActuatorStatusMsg& pm = msg.infoActuatorStatus(); pm.init(count, ch, radio().rssi()); if ( ack == false ) { pm.clearAck(); } //ch.patchStatus(msg); sendFake(msg, from); ch.changed(false); } void sendInfoParamResponsePairs(HMID to, uint8_t count, const GenericList& list) { InfoParamResponsePairsMsg& pm = msg.infoParamResponsePairs(); // setup message for maximal size pm.init(count); uint8_t current = 0; uint8_t* buf = pm.data(); for ( int i = 0; i < list.getSize(); ++i ) { *buf++ = list.getRegister(i); *buf++ = list.getByte(i); current++; if ( current == 8 ) { // reset to zero current = 0; buf = pm.data(); if ( send(msg, to) == false ) { // exit loop in case of error break; } } } *buf++ = 0; *buf++ = 0; current++; pm.entries(current); pm.clearAck(); send(msg, to); } template void sendInfoPeerList (HMID to, uint8_t count, const ChannelType& channel) { InfoPeerListMsg& pm = msg.infoPeerList(); // setup message for maximal size pm.init(count); uint8_t current = 0; uint8_t* buf = pm.data(); for ( uint8_t i = 0; i < channel.peers(); ++i ) { Peer p = channel.peerat(i); if ( p.valid() == true ) { memcpy(buf, &p, sizeof(Peer)); buf += sizeof(Peer); current++; if ( current == 4 ) { // reset to zero current = 0; buf = pm.data(); if ( send(msg, to) == false ) { // exit loop in case of error break; } } } } memset(buf, 0, sizeof(Peer)); current++; pm.entries(current); pm.clearAck(); send(msg, to); } void sendMasterEvent (Message& msg) { send(msg, getMasterID()); hal->sendPeer(); } template void sendPeerEvent (Message& msg, const ChannelType& ch) { bool sendtopeer = false; for ( int i = 0; i < ch.peers(); ++i ) { Peer p = ch.peerat(i); if ( p.valid() == true ) { // skip if this is not the first peer of that device if ( ch.peerfor(p) < i ) { continue; } if ( isDeviceID(p) == true ) { // we send to ourself - no ack needed getDeviceID(msg.from()); msg.to(msg.from()); if ( msg.ackRequired() == true ) { msg.clearAck(); this->process(msg); msg.setAck(); } else { this->process(msg); } } else { // check if burst needed for peer typename ChannelType::List4 l4 = ch.getList4(p); msg.burstRequired( l4.burst() ); send(msg, p); sendtopeer = true; } } } // if we have no peer - send to master/broadcast if ( sendtopeer == false ) { send(msg, getMasterID()); } // signal that we have send to peer hal->sendPeer(); } template void broadcastPeerEvent (Message& msg, const ChannelType& ch) { getDeviceID(msg.from()); msg.clearAck(); // check if we are peered to ourself if ( ch.peerfor(msg.from()) < ch.peers() ) { msg.to(msg.from()); // simply process this->process(msg); } HMID todev; bool burst = false; // go over all peers, get first external device // check if one of the peers needs a burst to wakeup for ( uint8_t i = 0; i < ch.peers(); ++i ) { Peer p = ch.peerat(i); if ( p.valid() == true ) { if ( msg.from() != p ) { if ( todev.valid() == false ) { todev = p; } typename ChannelType::List4 l4 = ch.getList4(p); burst |= l4.burst(); } } } // if we have no external device - send to master/broadcast if ( todev.valid() == false ) { todev = getMasterID(); } // DPRINT("BCAST to: ");todev.dump(); DPRINTLN("\n"); msg.burstRequired(burst); msg.setBroadcast(); send(msg, todev); // signal that we have send to peer hal->sendPeer(); } void writeList (const GenericList& list, const uint8_t* data, uint8_t length) { for ( uint8_t i = 0; i < length; i += 2, data += 2 ) { list.writeRegister(*data, *(data + 1)); } } bool waitForAck(Message& msg, uint8_t timeout) { do { if ( radio().readAck(msg) == true ) { return true; } _delay_ms(10); // wait 10ms timeout--; } while ( timeout > 0 ); return false; } bool waitResponse(const Message& msg, Message& response, uint8_t timeout) { do { uint8_t num = radio().read(response); if ( num > 0 ) { DPRINT(F("-> ")); response.dump(); if ( msg.count() == response.count() && msg.to() == response.from() ) { return true; } } _delay_ms(10); // wait 10ms timeout--; } while ( timeout > 0 ); return false; } #ifdef USE_AES void sendAckAes (Message& msg, const uint8_t* data) { msg.ackAes().init(data); send(msg, msg.from()); } bool requestSignature(const Message& msg) { // no signature for internal message processing needed if ( isDeviceID(msg.from()) == true ) { return true; } // signing only possible if sender requests ACK if ( msg.ackRequired() == true ) { AesChallengeMsg signmsg; signmsg.init(msg, kstore.getIndex()); kstore.challengeKey(signmsg.challenge(), kstore.getIndex()); // TODO re-send message handling DPRINT(F("<- ")); signmsg.dump(); radio().write(signmsg, signmsg.burstRequired()); // read answer if ( waitForAesResponse(msg.from(), signmsg, 60) == true ) { AesResponseMsg& response = signmsg.aesResponse(); // DPRINT("AES ");DHEX(response.data(),16); // fill initial vector with message to sign kstore.fillInitVector(msg); // DPRINT("IV ");DHEX(iv,16); // decrypt response uint8_t* data = response.data(); aes128_dec(data, &kstore.ctx); // xor encrypted data with initial vector kstore.applyVector(data); // store data for sending ack kstore.storeAuth(response.count(), data); // decrypt response aes128_dec(data, &kstore.ctx); // DPRINT("r "); DHEX(response.data()+6,10); // DPRINT("s "); DHEX(msg.buffer(),10); // compare decrypted message with original message if ( memcmp(data + 6, msg.buffer(), 10) == 0 ) { DPRINTLN(F("Signature OK")); return true; } else { DPRINTLN(F("Signature FAILED")); } } else { DPRINTLN(F("waitForAesResponse failed")); } } return false; } bool processChallenge(const Message& msg, const uint8_t* challenge, uint8_t keyidx) { if ( kstore.challengeKey(challenge, keyidx) == true ) { DPRINT("Process Challenge - Key: "); DHEXLN(keyidx); AesResponseMsg answer; answer.init(msg); // fill initial vector with message to sign kstore.fillInitVector(msg); uint8_t* data = answer.data(); for ( uint8_t i = 0; i < 6; ++i ) { data[i] = (uint8_t)rand(); } memcpy(data + 6, msg.buffer(), 10); // TODO - check message to short possible aes128_enc(data, &kstore.ctx); kstore.applyVector(data); aes128_enc(data, &kstore.ctx); return send(answer, msg.to()); } return false; } bool waitForAesResponse(const HMID& from, Message& answer, uint8_t timeout) { do { uint8_t num = radio().read(answer); if ( num > 0 ) { DPRINT(F("-> ")); answer.dump(); if ( answer.isResponseAes() && from == answer.from() ) { return true; } } _delay_ms(10); // wait 10ms timeout--; } while ( timeout > 0 ); return false; } #endif }; } #endif