/*
 * disk/copylock.c
 * 
 * Rob Northen CopyLock protection track (Amiga).
 * 
 * Written in 2011 by Keir Fraser
 * 
 * RAW TRACK LAYOUT:
 *  518 decoded bytes per sector (excluding sector gap)
 *  Inter-sector gap of ~44 decoded zero bytes (44 MFM words).
 * Decoded Sector:
 *  u8 0xa0+index,0,0 :: First byte is MFM-illegal for TRKTYP_copylock_old
 *  <sync word>   :: Per-sector sync marker, see code for list
 *  u8 index      :: 0-11, must correspond to appropriate sync marker
 *  u8 data[512]
 *  u8 0
 * Data Bytes:
 *  Generated by a 23-bit LFSR, with taps at positions 1 and 23.
 *  The data bytes are an arbitrary fixed 8-bit window on the LFSR state.
 *  The generated byte stream carries across sector boundaries.
 * Sector 6:
 *  First 16 bytes interrupt random stream with signature "Rob Northen Comp"
 *  The LFSR-generated data then continues uninterrupted at 17th byte.
 *  (NB. Old-style Copylock with different sync marks does interrupt the
 *       LFSR stream for the "Rob Northen Comp" signature").
 * MFM encoding:
 *  In place, no even/odd split.
 * Timings:
 *  Sync 0x8912 is 5% faster; Sync 0x8914 is 5% slower. All other bit cells
 *  are 2us, and total track length is exactly as usual (the short sector
 *  precisely balances the long sector). Speed changes occur at the start of
 *  the preceding sector's gap, presumably to allow the disk controller to
 *  lock on.
 * 
 * TRKTYP_copylock data layout:
 *  u32 lfsr_seed; (Only lfsr_seed[22:0] is used and non-zero)
 *  First data byte of sector 0 is lfsr_seed[22:15].
 */

#include <libdisk/util.h>
#include <private/disk.h>

struct copylock_info {
    uint32_t lfsr_seed;
    uint8_t sec6_lfsr_skips_sig;
    uint8_t ext_sig_id;
};

struct copylock_ext_info {
    struct copylock_info info;
    uint32_t latency[11];
    uint32_t nr_valid_blocks;
    unsigned int least_block;
};

/* APB (SPS #1240) and Weird Dreams (SPS #2100) have an extended signature 
 * in sector 6. The extended signature differs, but the titles do share the 
 * same LFSR seed. */
#define EXT_SIG_LFSR_SEED 0x3e2896
struct copylock_extended_signature {
    const char *name;
    uint8_t sig_bytes[8];
} ext_sig[] = {
    { "APB",
      { 0x54, 0xe1, 0xed, 0x5b, 0x64, 0x85, 0x22, 0x7d } },
    { "Weird Dreams",
      { 0x78, 0x26, 0x46, 0xf4, 0xd5, 0x24, 0xa0, 0x03 } },
};

static const uint16_t sync_list[] = {
    0x8a91, 0x8a44, 0x8a45, 0x8a51, 0x8912, 0x8911,
    0x8914, 0x8915, 0x8944, 0x8945, 0x8951 };
static const uint8_t sec6_sig[] = {
    0x52, 0x6f, 0x62, 0x20, 0x4e, 0x6f, 0x72, 0x74, /* "Rob Northen Comp" */
    0x68, 0x65, 0x6e, 0x20, 0x43, 0x6f, 0x6d, 0x70 };

static uint32_t lfsr_prev(uint32_t x)
{
    return (x >> 1) | ((((x >> 1) ^ x) & 1) << 22);
}

static uint32_t lfsr_next(uint32_t x)
{
    return ((x << 1) & ((1u << 23) - 1)) | (((x >> 22) ^ x) & 1);
}

static uint32_t lfsr_backward(uint32_t x, unsigned int delta)
{
    while (delta--)
        x = lfsr_prev(x);
    return x;
}

static uint32_t lfsr_forward(uint32_t x, unsigned int delta)
{
    while (delta--)
        x = lfsr_next(x);
    return x;
}

static uint8_t lfsr_byte(uint32_t x)
{
    return (uint8_t)(x >> 15);
}

/* Take LFSR state from start of one sector, to another. */
static uint32_t lfsr_seek(
    struct copylock_info *info, uint32_t x,
    unsigned int from, unsigned int to)
{
    unsigned int sz;

    while (from != to) {
        if (from > to)
            from--;
        sz = 512;
        if (from == 6)
            sz -= sizeof(sec6_sig);
        if (!info->sec6_lfsr_skips_sig && (from == 5))
            sz += sizeof(sec6_sig);
        while (sz--)
            x = (from < to) ? lfsr_next(x) : lfsr_prev(x);
        if (from < to)
            from++;
    }

    return x;
}

static bool_t lfsr_check(uint32_t lfsr, const uint8_t *dat, unsigned int nr)
{
    while (nr) {
        if (*dat++ != lfsr_byte(lfsr))
            break;
        lfsr = lfsr_next(lfsr);
        nr--;
    }
    return nr == 0;
}

/* Does sector-map validity have a discontiguity at specified sector? */
static bool_t secmap_discontiguity_at(struct track_info *ti, unsigned int nr)
{
    unsigned int sec, min = ~0u, max = 0;
    for (sec = 0; sec < ti->nr_sectors; sec++) {
        if (!is_valid_sector(ti, sec))
            continue;
        if (min > sec)
            min = sec;
        max = sec;
    }
    return (max < nr) || (min >= nr);
}

bool_t track_is_copylock(struct track_info *ti)
{
    return ((ti->type == TRKTYP_copylock)
            || (ti->type == TRKTYP_copylock_old));
}

static void copylock_decode(
    struct track_info *ti, struct stream *s, struct copylock_ext_info *ext)
{
    ext->least_block = ~0u;

    while ((stream_next_bit(s) != -1) &&
           (ext->nr_valid_blocks != ti->nr_sectors)) {

        uint8_t dat[2*512];
        uint32_t lfsr, idx_off = s->index_offset_bc - 15;
        uint32_t lfsr_seed;
        unsigned int i, j, sec;

        /* Are we at the start of a sector we have not yet analysed? */
        if (ti->type == TRKTYP_copylock) {
            for (sec = 0; sec < ti->nr_sectors; sec++)
                if ((uint16_t)s->word == sync_list[sec])
                    break;
        } else /* TRKTYP_copylock_old */ {
            if (((uint16_t)s->word & 0xff00u) != 0x6500u)
                continue;
            sec = mfm_decode_word(s->word) & 0xfu;
            if (s->word != (mfm_encode_word(0xb0+sec) | (1u<<13)))
                continue;
        }
        if ((sec >= ti->nr_sectors) || is_valid_sector(ti, sec))
            continue;

        /* Check the sector header. */
        if (stream_next_bits(s, 16) == -1)
            break;
        if (mfm_decode_word((uint16_t)s->word) != sec)
            continue;

        /* Read and decode the sector data. */
        s->latency = 0;
        if (stream_next_bytes(s, dat, sizeof(dat)) == -1)
            break;
        mfm_decode_bytes(bc_mfm, sizeof(dat)/2, dat, dat);

        /* Deal with sector 6 preamble. */
        i = 0;
        if (sec == 6) {
            if (memcmp(dat, sec6_sig, sizeof(sec6_sig)))
                continue;
            i = sizeof(sec6_sig);
        }

        /* Get the LFSR start value for this sector. If we know the track LFSR
         * seed then we work it out from that, else we get it from sector
         * data. */
        if ((lfsr_seed = ext->info.lfsr_seed) == 0) {
            j = 256; /* An arbitray point at which to sample the LFSR */
            lfsr = (dat[j] << 15) | (dat[j+8] << 7) | (dat[j+16] >> 1);
            lfsr = lfsr_backward(lfsr, j-i);
            lfsr_seed = lfsr_seek(&ext->info, lfsr, sec, 0);
        } else {
            lfsr = lfsr_seek(&ext->info, lfsr_seed, 0, sec);
        }

        /* Check that the data matches the LFSR-generated stream. */
        if (!lfsr_check(lfsr, &dat[i], 512-i)) {
            if ((sec == 6) && (lfsr_seed == EXT_SIG_LFSR_SEED)) {
                /* This track may have the signature extended by 8 bytes. */
                for (j = 0; j < ARRAY_SIZE(ext_sig); j++)
                    if (!memcmp(ext_sig[j].sig_bytes, &dat[i], 8))
                        ext->info.ext_sig_id = j+1;
                if (!ext->info.ext_sig_id)
                    continue;
                /* Matched an extended signature: re-check rest of sector. */
                lfsr = lfsr_forward(lfsr, 8);
                if (!lfsr_check(lfsr, &dat[i+8], 512-i-8))
                    continue;
            } else {
                /* This sector is a fail. */
                continue;
            }
        }

        /* All good. Finally, stash the LFSR seed if we didn't know it. */
        if (ext->info.lfsr_seed == 0) {
            /* Paranoia: Reject the degenerate case of endless zero bytes. */
            if (lfsr_seed == 0)
                continue;
            ext->info.lfsr_seed = lfsr_seed;
        }

        /* Good sector: remember its details. */
        ext->latency[sec] = s->latency;
        set_sector_valid(ti, sec);
        ext->nr_valid_blocks++;
        if (ext->least_block > sec) {
            ti->data_bitoff = idx_off;
            ext->least_block = sec;
        }
    }
}

static void *copylock_write_raw(
    struct disk *d, unsigned int tracknr, struct stream *s)
{
    struct track_info *ti = &d->di->track[tracknr];
    struct copylock_ext_info ext = {
        .info.sec6_lfsr_skips_sig = (ti->type == TRKTYP_copylock) };
    unsigned int sec;
    struct copylock_info *info;

    copylock_decode(ti, s, &ext);

    if (ext.nr_valid_blocks == 0)
        return NULL;

    /* One variant of copylock_old does skip the LFSR across the signature
     * at the start of sector 6. We try to auto-detect that here. */
    if ((ti->type == TRKTYP_copylock_old) && secmap_discontiguity_at(ti, 6)) {
        struct track_info new_ti = { 0 };
        struct copylock_ext_info new_ext = {
            .info.sec6_lfsr_skips_sig = TRUE };
        memset(&new_ti, 0, sizeof(new_ti));
        init_track_info(&new_ti, ti->type);
        stream_reset(s);
        copylock_decode(&new_ti, s, &new_ext);
        if (!secmap_discontiguity_at(&new_ti, 6)) {
            /* Variant Copylock decode is an improvement, so let's use that. */
            ext = new_ext;
            *ti = new_ti;
        }
    }

    /* Check validity of the non-uniform track timings. */
    if (!is_valid_sector(ti, 5))
        ext.latency[5] = 514*8*2*2000; /* bodge it */
    for (sec = 0; sec < ti->nr_sectors; sec++) {
        float d = (100.0 * ((int)ext.latency[sec] - (int)ext.latency[5]))
            / (int)ext.latency[5];
        if (!is_valid_sector(ti, sec))
            continue;
        switch (sec) {
        case 4:
            if (d > -4.0)
                trk_warn(ti, tracknr, "Short sector is only "
                         "%.2f%% different", d);
            break;
        case 6:
            if (d < 4.0)
                trk_warn(ti, tracknr, "Long sector is only "
                         "%.2f%% different", d);
            break;
        default:
            if ((d < -2.0) || (d > 2.0))
                trk_warn(ti, tracknr, "Normal sector is "
                         "%.2f%% different", d);
            break;
        }
    }

    /* Adjust track offset for any missing initial sectors. */
    for (sec = 0; sec < ti->nr_sectors; sec++)
        if (is_valid_sector(ti, sec))
            break;
    ti->data_bitoff -= sec * (514+48)*8*2;

    /* Adjust track offset for first sector's sync-mark offset. */
    ti->data_bitoff -= 3*8*2;

    /* Magic: We can reconstruct the entire track from the LFSR seed! */
    if (ext.nr_valid_blocks != ti->nr_sectors) {
        trk_warn(ti, tracknr, "Reconstructed damaged track (%u bad sectors)",
                 ti->nr_sectors - ext.nr_valid_blocks);
        set_all_sectors_valid(ti);
    }

    ti->len = sizeof(ext.info);
    info = memalloc(ti->len);
    *info = ext.info;
    info->lfsr_seed = htobe32(info->lfsr_seed);
    return info;
}

static void copylock_read_raw(
    struct disk *d, unsigned int tracknr, struct tbuf *tbuf)
{
    struct track_info *ti = &d->di->track[tracknr];
    struct copylock_info *info = (struct copylock_info *)ti->dat;
    uint32_t lfsr, lfsr_seed = be32toh(info->lfsr_seed);
    unsigned int i, j, sec = 0;
    uint16_t speed = SPEED_AVG;

    tbuf_disable_auto_sector_split(tbuf);

    while (sec < ti->nr_sectors) {
        /* Header */
        if (ti->type == TRKTYP_copylock) {
            tbuf_bits(tbuf, speed, bc_mfm, 8, 0xa0 + sec);
            tbuf_bits(tbuf, speed, bc_mfm, 16, 0);
            tbuf_bits(tbuf, speed, bc_raw, 16, sync_list[sec]);
        } else /* TRKTYP_copylock_old */ {
            tbuf_bits(tbuf, speed, bc_raw, 16,
                      mfm_encode_word(0xa0 + sec) | (1u<<13));
            tbuf_bits(tbuf, speed, bc_mfm, 16, 0);
            tbuf_bits(tbuf, speed, bc_raw, 16,
                      mfm_encode_word(0xb0 + sec) | (1u<<13));
        }
        tbuf_bits(tbuf, speed, bc_mfm, 8, sec);
        /* Data */
        lfsr = lfsr_seek(info, lfsr_seed, 0, sec);
        for (i = 0; i < 512; i++) {
            if ((sec == 6) && (i == 0)) {
                for (i = 0; i < sizeof(sec6_sig); i++)
                    tbuf_bits(tbuf, speed, bc_mfm, 8, sec6_sig[i]);
                if (info->ext_sig_id) {
                    struct copylock_extended_signature *sig
                        = &ext_sig[info->ext_sig_id-1];
                    for (j = 0; j < 8; j++)
                        tbuf_bits(tbuf, speed, bc_mfm, 8, sig->sig_bytes[j]);
                    lfsr = lfsr_forward(lfsr, 8);
                }
            }
            tbuf_bits(tbuf, speed, bc_mfm, 8, lfsr_byte(lfsr));
            lfsr = lfsr_next(lfsr);
        }
        /* Footer */
        tbuf_bits(tbuf, speed, bc_mfm, 8, 0);

        /* Move to next sector's speed to encode track gap. */
        sec++;
        speed = (sec == 4 ? (SPEED_AVG * 95) / 100 :
                 sec == 6 ? (SPEED_AVG * 105) / 100 :
                 SPEED_AVG);
        tbuf_gap(tbuf, speed, 44*8);
    }
}

static void copylock_get_name(
    struct disk *d, unsigned int tracknr, char *str, size_t size)
{
    struct track_info *ti = &d->di->track[tracknr];
    struct copylock_info *info = (struct copylock_info *)ti->dat;
    int len;

    len = snprintf(str, size, "%s", ti->typename);
    if ((ti->type == TRKTYP_copylock) ^ info->sec6_lfsr_skips_sig)
        len += snprintf(str+len, size-len, " (Variant)");
    if (info->ext_sig_id)
        len += snprintf(str+len, size-len, " (%s Signature)",
                        ext_sig[info->ext_sig_id-1].name);
}

struct track_handler copylock_handler = {
    .bytes_per_sector = 512,
    .nr_sectors = 11,
    .write_raw = copylock_write_raw,
    .read_raw = copylock_read_raw,
    .get_name = copylock_get_name
};

struct track_handler copylock_old_handler = {
    .bytes_per_sector = 512,
    .nr_sectors = 11,
    .write_raw = copylock_write_raw,
    .read_raw = copylock_read_raw,
    .get_name = copylock_get_name
};

/*
 * Local variables:
 * mode: C
 * c-file-style: "Linux"
 * c-basic-offset: 4
 * tab-width: 4
 * indent-tabs-mode: nil
 * End:
 */
