/*                                       -*- mode:C++; fill-column: 100 -*-
  SFBCodeFlow.cpp - routines for managing code movement between SFBs
  Copyright (C) 2009 The Regents of the University of New Mexico.  All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
  USA
*/

#include "SFBCodeFlow.h"
#include "SFBAssert.h"
#include "SFBPacket.h"     /* For packetLength, etc */
#include "SFBReactor.h"    /* For Body */
#include "SFBReflex.h"     /* For GET_REFLEX_FLAG */
#include "SFBSerial.h"     /* For Faces */
#include "SFBRandom.h"     /* For random() */
#include "SFBPrintf.h"     /* For logging routines */
#include "SFBBootBlock.h"  /* For getBootBlockFlags, BOOTBLOCK_NO_UPDATES */

static CodeFlowStatusCallback theStatusCallback IN_BODYRAM_BSS;

static void notifyStatus(u8 face, CodeFlowStatus cfs) {
  if (theStatusCallback)
    theStatusCallback(face,cfs);
}

void setCodeFlowStatusCallback(CodeFlowStatusCallback ptr) {
  theStatusCallback = ptr;
}

u8 sectorOfAddress(u32 address) {
  if (address < 0x8000)  return address >> 12;        // Sectors 0..7, 4K each
  if (address < 0x78000) return (address >> 15) + 7;  // Sectors 8..21, 32K each
  if (address < 0x7E000) return (address >> 12) - 98; // Sectors 22..27, 4K each
  if (address == 0x7E000) return (address >> 12) - 98; // Allow start of sector 28 as boundary case
  API_BUG(E_API_BAD_SECTOR); 
}

u32 sectorStart(u8 sectorNumber) {
  if (sectorNumber < 8)  return sectorNumber << 12;        // Sectors 0..7, 4K each
  if (sectorNumber < 22) return (sectorNumber - 7) << 15;  // Sectors 8..21, 32K each
  if (sectorNumber < 28) return (sectorNumber + 98) << 12; // Sectors 22..27, 4K each
  if (sectorNumber == 28) return (sectorNumber + 98) << 12; // Allow start of sector 28 as boundary case
  API_BUG(E_API_BAD_SECTOR);
}

u32 sectorSize(u8 sectorNumber) {
  if (sectorNumber < 8)  return  1 << 12; // Sectors 0..7, 4K each
  if (sectorNumber < 22) return  1 << 15; // Sectors 8..21, 32K each
  if (sectorNumber < 28) return  1 << 12; // Sectors 22..27, 4K each
  API_BUG(E_API_BAD_SECTOR);
}

u32 sectorEnd(u8 sectorNumber) {
  return sectorStart(sectorNumber)+sectorSize(sectorNumber)-1;
}

/**
 * Do limited checks on the potential validity of a provenance,
 * considering sanity checks that depend only on the contents of the
 * provenance block itself.  (Meaning: No checksum computation, and no
 * footer checking.)  plausibleProvenance can be called on a
 * 'detached' provenance, so long as the startAddr and endAddr are
 * specified properly.  For size computation purposes, it is assumed
 * that the provenance would be found directly at startAddr.
 */

bool plausibleProvenance(SFBProvenance * p, uptr startAddr, uptr endAddr) {

  if (startAddr+sizeof(SFBProvenance) > endAddr) return false; // Can it fit?
  if (p->magic != SFB_PROVENANCE_MAGIC) return false;
  if (p->cigam != SFB_PROVENANCE_CIGAM) return false;
  if (p->creatorId == -1) return false;
  if (p->programId == -1) return false;
  if (p->buildDate < 0) return false;
  if (p->buildTime < 0) return false;
  if (p->programLength < 0) return false;
  if ((p->programLength&3) != 0) return false;

  // Alleged program fit in memory?
  uptr end = startAddr+p->programLength;
  if (end > endAddr) return false;

  return true;
}

/**
 * Do a full check of a provenance+contiguous program.  The entire
 * address range implied by the provenance and its program length must
 * fit in startAddr..endAddr, the provenance must be sane, and the
 * checksum may be calculated and checked or stored, as indicated by
 * checksumMode.
 *
 * A 'detached' provenance cannot be checked by this method.  (See
 * plausibleProvenance).
 */

bool validProvenance(SFBProvenance * p, uptr startAddr, uptr endAddr, int checksumMode) {

  if (!plausibleProvenance(p,startAddr,endAddr))
    return false;

  // Program footer present?
  uptr end = startAddr+p->programLength;
  if (end > endAddr) return false;
  u32 * endu = (u32*) end;
  if (endu[-1] != SFB_PROVENANCE_NZTRX) return false;

  if (checksumMode == CHECKSUM_IGNORE)
    return true;

  // Well, we have to do the heavy lifting..
  SFBChecksum cs;
  SFBChecksumInit(cs);
  SFBChecksumAddBytes(cs, 
                      (const char *) &p->magic,
                      p->programLength-STRUCT_MEMBER_OFFSET(SFBProvenance,magic)); 

  if (checksumMode == CHECKSUM_CHECK)
    return p->checksum0 == cs[0] && p->checksum1 == cs[1];

  if (checksumMode == CHECKSUM_GENERATE) {
    p->checksum0 = cs[0];
    p->checksum1 = cs[1];
    return true;
  }

  API_BUG(E_API_ARGUMENT);
}

SFBProvenance * findProvenance(uptr startAddr, uptr endAddr, int checksumMode) {
  API_ASSERT_WORD_ALIGNED(startAddr);
  API_ASSERT_WORD_ALIGNED(endAddr);

  u32* plausibleEnd = (u32*) (endAddr-sizeof(SFBProvenance));
  for (u32 * addr = (u32*) startAddr; addr < plausibleEnd; ++addr) {
    SFBProvenance * candidate = (SFBProvenance*) addr;
    if (candidate->magic == SFB_PROVENANCE_MAGIC) {
      if (validProvenance(candidate,(uptr) candidate,endAddr,checksumMode)) 
        return (SFBProvenance*) candidate;
    }
  }
  return 0;
}

enum { STATE_IDLE, STATE_ACTIVE, STATE_TIMEOUT1, STATE_TIMEOUT2, STATE_TIMEOUT3 };
/**
 * Information about the current sketch being offered for download to other SFB's, plus state
 * associated with such downloads.
 */
static struct ProgramServer {
  volatile const SFBProvenance * theSketch;
  u32 theSketchLowAddress;
  u32 theSketchStartAddress;
  u32 theSketchHighAddress;
  uptr addressOffset;
  int alarmNum;
  u32 lastDataSentTime;
  u16 rnonces[FACE_COUNT];
  u16 snonces[FACE_COUNT];
  u16 states[FACE_COUNT];
  u8 programmingFlags;
  u8 active;
} ps IN_BODYRAM_BSS;

static void reset(u32 face) {
  notifyStatus(face,CODE_FLOW_IDLE);
  ps.states[face] = STATE_IDLE;
  ps.rnonces[face] = 0;
  ps.snonces[face] = 0;
  facePrintf(face,"PF\n");
}
static void trash(u32 face, u8 * packet) {
  notifyStatus(face,CODE_FLOW_IDLE);
  ps.states[face] = STATE_IDLE;
  ps.rnonces[face] = 0;
  ps.snonces[face] = 0;
  facePrintf(face,"P?%p\n",packet,packetLength(packet));
}

#define VERBOSE() (getLogLevel()>=LOGLEVEL_VERBOSE)
#define DEBUG() (getLogLevel()>=LOGLEVEL_DEBUG)

/* If we are linked with code that included SFBProvenanceData.h, as in
 * the case of a sketch, its wantPhPacketCodeUpdate will override this
 * one, and determine which updates are relevant.
 *
 * If no other wantPhPacketCodeUpdate is linked -- as in the case of
 * the host side tools (sfbmon, sfbprog) which cannot do hot code
 * updates -- this default stub will just say 'we don't want it'.
 */

int wantPhPacketCodeUpdate(u8 * ) {
  return 100;
}

void programServerDispatcher(u8 * packet) {

  if (TEST_REFLEX_FLAGS(RF_NO_CODE_FLOW)) // Nothing to do if we don't flow code
    return;

  if (getBootBlockBootMode()==RED_BOOT_MODE && // Code doesn't flow out of red boot mode, by default
      !TEST_REFLEX_FLAGS(RF_RED_BOOT_CODE_FLOW))
    return;

  int face = Body.source();
  if (!ps.active || !Faces[face].backgroundProcessingActive()) {
    Body.triggerUp();
    return;
  }

  API_ASSERT_NONNULL(ps.theSketch); // If we're active we must have a sketch

  u8 result;

  u32 matches = packetScanf(packet,"P%c",&result);
  if (matches==0) { Body.triggerUp(); return; } // Not a 'P' packet
  if (matches!=2) { trash(face,packet); return; } // Some bogus crap
  
  logDebug("[Got P%c, len = %d, flags=%x, p=%#p]\n",
           result,packetLength(packet),packetFlags(packet),packet);
  switch (result) {

  default: 
    logDebug("[Unrec pkt from %s - 'P%c']\n",FACE_NAME(face),packet[1]);
    trash(face,packet); 
    return; 

  case 'd':
    if (packetReadEOF(packet)) {
      if (getLogLevel() == LOGLEVEL_NORMAL)
        setLogLevel(LOGLEVEL_VERBOSE);
      else if (getLogLevel() == LOGLEVEL_VERBOSE)
        setLogLevel(LOGLEVEL_DEBUG);
      else
        setLogLevel(LOGLEVEL_NORMAL);
      logNormal("[Log level now %d]\n",getLogLevel());
    }
    break;

  case '?': 
    logDebug("[Packet:%p]\n",packet);
    break;

  case 'H': 
    break;

  case 'l': 
    Faces[face].println("PL-");
    break;

  case 'L': {
    u8 ch;
    u32 sid,pid,bdt,btm;
    if (packetScanf(packet,"%c%l%l%l%l",&ch,&sid,&pid,&bdt,&btm) != 5) {
      trash(face,packet); 
      return; 
    }
    if (ch != '+')           // Not in the bootloader, or doesn't know
      return;
    logVerbose("[%s loader is %t:%t/%x-%x]\n",FACE_NAME(face),sid,pid,bdt,btm);
    break;
  }

  case 'a': {
    u16 rnonce; u8 arch;
    if (packetScanf(packet,"%h%c",&rnonce, &arch) != 2) { trash(face,packet); return; }
    if (arch == ps.theSketch->architecture) { // We have code for that..
      ps.rnonces[face] = rnonce;
      ps.snonces[face] = random(0xffff)+1; // 0x1..0xffff
      logNormal("[%s seeking arch %d code, responding]\n",FACE_NAME(face),arch);

      ps.lastDataSentTime = millis();

      facePrintf(face, "Pl\n"); // Include a loader version request..
      facePrintf(face, "PA%h%h%c%c%l%l%l%l%l%l%l%l%l%l%\n",
                 ps.rnonces[face], ps.snonces[face],
                 arch, ps.programmingFlags,
                 getBootBlockOwnerId(), getBootBlockBoardId(),
                 ps.theSketch->creatorId, ps.theSketch->programId,
                 ps.theSketch->buildDate, ps.theSketch->buildTime,
                 ps.theSketchStartAddress, ps.theSketch->programLength,
                 ps.theSketch->checksum0, ps.theSketch->checksum1);
    }
    facePrintf(face,"PA%h\n",ps.rnonces[face]);  // And that's all we have
    break;
  }
  case 'c':  {
    notifyStatus(face,CODE_FLOW_ACTIVE);
    ps.states[face] = STATE_ACTIVE;
    u16 snonce;
    u32 addr, len;
    if (packetScanf(packet,"%h%l%l",&snonce,&addr,&len) != 3 || snonce==0) { trash(face,packet); return; }
    logDebug("[Pc %d (vs %d) addr 0x%x len %d end 0x%x lo 0x%x hi 0x%x ]\n",
             snonce,ps.snonces[face],addr,len,addr+len,ps.theSketchLowAddress,ps.theSketchHighAddress);
    
    if (snonce != ps.snonces[face]) { reset(face); return; }
    if (addr < ps.theSketchLowAddress) { trash(face,packet); return; }
    if (addr+len > ps.theSketchHighAddress) { trash(face,packet); return; }

    // OK, they've asked politely for a checksum; we'll do it.
    SFBChecksum cs;
    SFBChecksumInit(cs);
    SFBChecksumAddBytes(cs, (char *) (addr+ps.addressOffset), len);

    logNormal("[Sending checksum %s for 0x%x+%d=%x-%x]\n",FACE_NAME(face),addr,len,cs[0],cs[1]);

    facePrintf(face, "PC%h%l%l%l%l%\n", ps.rnonces[face], addr, len, cs[0], cs[1]);
    break;
  }
  case 'b':  {
    notifyStatus(face,CODE_FLOW_ACTIVE);
    ps.states[face] = STATE_ACTIVE;
    u16 snonce;
    u8 clen;
    u32 addr, len;
    if (packetScanf(packet,"%h%l%c",&snonce,&addr,&clen) != 3 || snonce==0) { trash(face,packet); return; }
    len = ((u32)clen)+1;        // 1..256
    if (snonce != ps.snonces[face]) { reset(face); return; }
    if (addr < ps.theSketchLowAddress) { trash(face,packet); return; }
    if (addr+len > ps.theSketchHighAddress) { 
      logDebug("[--request end at 0x%x above high at 0x%x]\n",addr+len,ps.theSketchHighAddress);
      trash(face,packet); 
      return; 
    }

    logDebug("[Sending data %s for 0x%x+%d]\n",FACE_NAME(face),addr);
    ps.lastDataSentTime = millis();

    facePrintf(face,"PB%h%l", ps.rnonces[face], addr);                   // Start the packet..
    Faces[face].print((u8*) (addr+ps.addressOffset),len);                // Dump the data..
    facePrintf(face,"%\n");                                              // End with checkbyte
    break;
  }
  case 'g': {                    
    notifyStatus(face,CODE_FLOW_ACTIVE);
    ps.states[face] = STATE_ACTIVE;
    u32 snonce;
    if (!packetRead(packet,snonce,BESHORT) || snonce != ps.snonces[face]) { trash(face,packet); return; }

    // Ah, so all went well; how nice.
    logNormal("[%s burned successfully!]\n",FACE_NAME(face));

    facePrintf(face,"PG%h\n",ps.rnonces[face]);  // They won't see this, but for symmetry

    notifyStatus(face,CODE_FLOW_SUCCEEDED); // ..We just programmed somebody, over there 'face'-way..

    break;
  }
  case 'h': 
    if (getBootBlockFlags()&BOOTBLOCK_NO_UPDATES) 
      logVerbose("[Ignored Ph - BOOTBLOCK_NO_UPDATES set]\n");
    else {
      packetReread(packet); // Rewind the stream since wantPh.. wants to read it all
      int code = wantPhPacketCodeUpdate(packet);

      if (ps.theSketch!=0) {
        logVerbose("[Ph: Us: arch=%d, sketch=%t:%t/%x-%x.  Code wanted: %d]\n",
                   ps.theSketch->architecture,
                   ps.theSketch->creatorId, ps.theSketch->programId,
                   ps.theSketch->buildDate, ps.theSketch->buildTime,
                   code);
      }
      if (code==0) reenterBrainstem();
      else Faces[Body.source()].println("PH");
      break;
    }
  }
}

void printPhSketchInfo(u8 toFace,const volatile SFBProvenance * theSketch) {
  if (TEST_REFLEX_FLAGS(RF_NO_CODE_FLOW))
    return;
  if (theSketch != 0) 
    facePrintf(toFace,"Ph%c%l%l%l%l%\n",
               theSketch->architecture,
               theSketch->creatorId, theSketch->programId,
               theSketch->buildDate, theSketch->buildTime);
}

bool announcePhSketchInfoIfAny(u8 toFace) {
  if (ps.theSketch != 0) {
    printPhSketchInfo(toFace,ps.theSketch);
    return true;
  }
  return false;
}

static void programmerTimeout(u32 when) {
  if (!TEST_REFLEX_FLAGS(RF_NO_CODE_FLOW)) {
    for (u32 i = NORTH; i <= WEST; ++i) {
      if (ps.states[i] != STATE_IDLE) {
        if (ps.states[i] == STATE_TIMEOUT3) {

          notifyStatus(i,CODE_FLOW_IDLE);
          logVerbose("[%s code flow idle]\n",FACE_NAME(i));

          ps.states[i] = STATE_IDLE;
          ps.rnonces[i] = 0;
          ps.snonces[i] = 0;
        } else ps.states[i]++;
      } else if (Faces[i].backgroundProcessingActive()) {
        if (randomBits(5)==0)     // 1-in-32 odds per 3 seconds
          printPhSketchInfo(i,ps.theSketch);
      }
    }
  }
  Alarms.set(ps.alarmNum,when+3000);
}

static void onceOnlyInits() {
  ps.alarmNum = Alarms.create(programmerTimeout);
  Alarms.set(ps.alarmNum,millis()+1000);
  ps.lastDataSentTime = millis();
  Body.reflexReflex('P',programServerDispatcher);
}

void programServerSetSketch(volatile const SFBProvenance * theSketch, u32 startAddress, uptr addressOffset, u8 pflags) {
  ps.theSketch = theSketch;
  if (ps.theSketch != 0) {
    ps.theSketchLowAddress = startAddress&~0xff;
    ps.theSketchHighAddress = startAddress+(theSketch->programLength|0xff)+1;
    ps.theSketchStartAddress = startAddress;
    ps.addressOffset = addressOffset;

    ps.active = 1;
  }

  if (ps.alarmNum==0) 
    onceOnlyInits();

  for (u32 face = NORTH; face <= WEST; ++face) {
    ps.rnonces[face] = 0;
    ps.snonces[face] = 0;
    ps.states[face] = STATE_IDLE;
    notifyStatus(face,CODE_FLOW_IDLE);
  }
    
  ps.programmingFlags = pflags;
}

void initProgramServerSketch() {
  ps.theSketch = 0;             // No program serving for you, unless somebody
  ps.active = 0;                // calls programServerSetSketch at some later time
}
