/*
 * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic
 * James S. Plank, Ethan L. Miller, Kevin M. Greenan,
 * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.
 *
 * gf_general.c
 *
 * This file has helper routines for doing basic GF operations with any
 * legal value of w.  The problem is that w <= 32, w=64 and w=128 all have
 * different data types, which is a pain.  The procedures in this file try
 * to alleviate that pain.  They are used in gf_unit and gf_time.
 */

#include <stdio.h>
#include <getopt.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <assert.h>

#include "gf_complete.h"
#include "gf_int.h"
#include "gf_method.h"
#include "gf_rand.h"
#include "gf_general.h"

void gf_general_set_zero(gf_general_t *v, int w)
{
  if (w <= 32) {
    v->w32 = 0;
  } else if (w <= 64) {
    v->w64 = 0;
  } else {
    v->w128[0] = 0;
    v->w128[1] = 0;
  }
}

void gf_general_set_one(gf_general_t *v, int w)
{
  if (w <= 32) {
    v->w32 = 1;
  } else if (w <= 64) {
    v->w64 = 1;
  } else {
    v->w128[0] = 0;
    v->w128[1] = 1;
  }
}

void gf_general_set_two(gf_general_t *v, int w)
{
  if (w <= 32) {
    v->w32 = 2;
  } else if (w <= 64) {
    v->w64 = 2;
  } else {
    v->w128[0] = 0;
    v->w128[1] = 2;
  }
}

int gf_general_is_zero(gf_general_t *v, int w) 
{
  if (w <= 32) {
    return (v->w32 == 0);
  } else if (w <= 64) {
    return (v->w64 == 0);
  } else {
    return (v->w128[0] == 0 && v->w128[1] == 0);
  }
}

int gf_general_is_one(gf_general_t *v, int w) 
{
  if (w <= 32) {
    return (v->w32 == 1);
  } else if (w <= 64) {
    return (v->w64 == 1);
  } else {
    return (v->w128[0] == 0 && v->w128[1] == 1);
  }
}

void gf_general_set_random(gf_general_t *v, int w, int zero_ok) 
{
  if (w <= 32) {
      v->w32 = MOA_Random_W(w, zero_ok);
  } else if (w <= 64) {
    while (1) {
      v->w64 = MOA_Random_64();
      if (v->w64 != 0 || zero_ok) return;
    }
  } else {
    while (1) {
      MOA_Random_128(v->w128);
      if (v->w128[0] != 0 || v->w128[1] != 0 || zero_ok) return;
    }
  }
}

void gf_general_val_to_s(gf_general_t *v, int w, char *s, int hex)
{
  if (w <= 32) {
    if (hex) {
      sprintf(s, "%x", v->w32);
    } else {
      sprintf(s, "%u", v->w32);
    }
  } else if (w <= 64) {
    if (hex) {
      sprintf(s, "%llx", (long long unsigned int) v->w64);
    } else {
      sprintf(s, "%lld", (long long unsigned int) v->w64);
    }
  } else {
    if (v->w128[0] == 0) {
      sprintf(s, "%llx", (long long unsigned int) v->w128[1]);
    } else {
      sprintf(s, "%llx%016llx", (long long unsigned int) v->w128[0], 
                                (long long unsigned int) v->w128[1]);
    }
  }
}

int gf_general_s_to_val(gf_general_t *v, int w, char *s, int hex)
{
  int l;
  int save;

  if (w <= 32) {
    if (hex) {
      if (sscanf(s, "%x", &(v->w32)) == 0) return 0;
    } else {
      if (sscanf(s, "%u", &(v->w32)) == 0) return 0;
    }
    if (w == 32) return 1;
    if (w == 31) {
      if (v->w32 & ((gf_val_32_t)1 << 31)) return 0;
      return 1;
    } 
    if (v->w32 & ~((1 << w)-1)) return 0;
    return 1;
  } else if (w <= 64) {
    if (hex) return (sscanf(s, "%llx", (long long unsigned int *) (&(v->w64))) == 1);
    return (sscanf(s, "%lld", (long long int *) (&(v->w64))) == 1);
  } else {
    if (!hex) return 0;
    l = strlen(s);
    if (l <= 16) {
      v->w128[0] = 0;
      return (sscanf(s, "%llx", (long long unsigned int *) (&(v->w128[1]))) == 1);
    } else {
      if (l > 32) return 0;
      save = s[l-16];
      s[l-16] = '\0';
      if (sscanf(s, "%llx", (long long unsigned int *) (&(v->w128[0]))) == 0) {
        s[l-16] = save;
        return 0;
      }
      return (sscanf(s+(l-16), "%llx", (long long unsigned int *) (&(v->w128[1]))) == 1);
    }
  }
}
    
void gf_general_add(gf_t *gf, gf_general_t *a, gf_general_t *b, gf_general_t *c)
{
  gf_internal_t *h;
  int w;

  h = (gf_internal_t *) gf->scratch;
  w = h->w;

  if (w <= 32) {
    c->w32 = a->w32 ^ b->w32;
  } else if (w <= 64) {
    c->w64 = a->w64 ^ b->w64;
  } else {
    c->w128[0] = a->w128[0] ^ b->w128[0];
    c->w128[1] = a->w128[1] ^ b->w128[1];
  }
}
  
void gf_general_multiply(gf_t *gf, gf_general_t *a, gf_general_t *b, gf_general_t *c)
{
  gf_internal_t *h;
  int w;

  h = (gf_internal_t *) gf->scratch;
  w = h->w;

  if (w <= 32) {
    c->w32 = gf->multiply.w32(gf, a->w32, b->w32);
  } else if (w <= 64) {
    c->w64 = gf->multiply.w64(gf, a->w64, b->w64);
  } else {
    gf->multiply.w128(gf, a->w128, b->w128, c->w128);
  }
}
  
void gf_general_divide(gf_t *gf, gf_general_t *a, gf_general_t *b, gf_general_t *c)
{
  gf_internal_t *h;
  int w;

  h = (gf_internal_t *) gf->scratch;
  w = h->w;

  if (w <= 32) {
    c->w32 = gf->divide.w32(gf, a->w32, b->w32);
  } else if (w <= 64) {
    c->w64 = gf->divide.w64(gf, a->w64, b->w64);
  } else {
    gf->divide.w128(gf, a->w128, b->w128, c->w128);
  }
}
  
void gf_general_inverse(gf_t *gf, gf_general_t *a, gf_general_t *b)
{
  gf_internal_t *h;
  int w;

  h = (gf_internal_t *) gf->scratch;
  w = h->w;

  if (w <= 32) {
    b->w32 = gf->inverse.w32(gf, a->w32);
  } else if (w <= 64) {
    b->w64 = gf->inverse.w64(gf, a->w64);
  } else {
    gf->inverse.w128(gf, a->w128, b->w128);
  }
}
  
int gf_general_are_equal(gf_general_t *v1, gf_general_t *v2, int w)
{
  if (w <= 32) {
    return (v1->w32 == v2->w32);
  } else if (w <= 64) {
    return (v1->w64 == v2->w64);
  } else {
    return (v1->w128[0] == v2->w128[0] &&
            v1->w128[1] == v2->w128[1]);
  }
}

void gf_general_do_region_multiply(gf_t *gf, gf_general_t *a, void *ra, void *rb, int bytes, int xor)
{
  gf_internal_t *h;
  int w;

  h = (gf_internal_t *) gf->scratch;
  w = h->w;

  if (w <= 32) {
    gf->multiply_region.w32(gf, ra, rb, a->w32, bytes, xor);
  } else if (w <= 64) {
    gf->multiply_region.w64(gf, ra, rb, a->w64, bytes, xor);
  } else {
    gf->multiply_region.w128(gf, ra, rb, a->w128, bytes, xor);
  }
}

void gf_general_do_region_check(gf_t *gf, gf_general_t *a, void *orig_a, void *orig_target, void *final_target, int bytes, int xor)
{
  gf_internal_t *h;
  int w, words, i;
  gf_general_t oa, ot, ft, sb;
  char sa[50], soa[50], sot[50], sft[50], ssb[50];

  h = (gf_internal_t *) gf->scratch;
  w = h->w;

  words = (bytes * 8) / w;
  for (i = 0; i < words; i++) {
    if (w <= 32) {
      oa.w32 = gf->extract_word.w32(gf, orig_a, bytes, i);
      ot.w32 = gf->extract_word.w32(gf, orig_target, bytes, i);
      ft.w32 = gf->extract_word.w32(gf, final_target, bytes, i);
      sb.w32 = gf->multiply.w32(gf, a->w32, oa.w32);
      if (xor) sb.w32 ^= ot.w32;
    } else if (w <= 64) {
      oa.w64 = gf->extract_word.w64(gf, orig_a, bytes, i);
      ot.w64 = gf->extract_word.w64(gf, orig_target, bytes, i);
      ft.w64 = gf->extract_word.w64(gf, final_target, bytes, i);
      sb.w64 = gf->multiply.w64(gf, a->w64, oa.w64);
      if (xor) sb.w64 ^= ot.w64;
    } else {
      gf->extract_word.w128(gf, orig_a, bytes, i, oa.w128);
      gf->extract_word.w128(gf, orig_target, bytes, i, ot.w128);
      gf->extract_word.w128(gf, final_target, bytes, i, ft.w128);
      gf->multiply.w128(gf, a->w128, oa.w128, sb.w128);
      if (xor) {
        sb.w128[0] ^= ot.w128[0];
        sb.w128[1] ^= ot.w128[1];
      }
    }

    if (!gf_general_are_equal(&ft, &sb, w)) {
      
      fprintf(stderr,"Problem with region multiply (all values in hex):\n");
      fprintf(stderr,"   Target address base: 0x%lx.  Word 0x%x of 0x%x.  Xor: %d\n", 
                 (unsigned long) final_target, i, words, xor);
      gf_general_val_to_s(a, w, sa, 1);
      gf_general_val_to_s(&oa, w, soa, 1);
      gf_general_val_to_s(&ot, w, sot, 1);
      gf_general_val_to_s(&ft, w, sft, 1);
      gf_general_val_to_s(&sb, w, ssb, 1);
      fprintf(stderr,"   Value: %s\n", sa);
      fprintf(stderr,"   Original source word: %s\n", soa);
      if (xor) fprintf(stderr,"   XOR with target word: %s\n", sot);
      fprintf(stderr,"   Product word: %s\n", sft);
      fprintf(stderr,"   It should be: %s\n", ssb);
      assert(0);
    }
  }
}

void gf_general_set_up_single_timing_test(int w, void *ra, void *rb, int size)
{
  void *top;
  gf_general_t g;
  uint8_t *r8, *r8a;
  uint16_t *r16;
  uint32_t *r32;
  uint64_t *r64;
  int i;

  top = (uint8_t *)rb+size;

  /* If w is 8, 16, 32, 64 or 128, fill the regions with random bytes.
     However, don't allow for zeros in rb, because that will screw up
     division.
     
     When w is 4, you fill the regions with random 4-bit words in each byte.

     Otherwise, treat every four bytes as an uint32_t
     and fill it with a random value mod (1 << w).
   */

  if (w == 8 || w == 16 || w == 32 || w == 64 || w == 128) {
    MOA_Fill_Random_Region (ra, size);
    while (rb < top) {
      gf_general_set_random(&g, w, 0);
      switch (w) {
        case 8: 
          r8 = (uint8_t *) rb;
          *r8 = g.w32;
          break;
        case 16: 
          r16 = (uint16_t *) rb;
          *r16 = g.w32;
          break;
        case 32: 
          r32 = (uint32_t *) rb;
          *r32 = g.w32;
          break;
        case 64:
          r64 = (uint64_t *) rb;
          *r64 = g.w64;
          break;
        case 128: 
          r64 = (uint64_t *) rb;
          r64[0] = g.w128[0];
          r64[1] = g.w128[1];
          break;
      }
      rb = (uint8_t *)rb + (w/8);
    }
  } else if (w == 4) {
    r8a = (uint8_t *) ra;
    r8 = (uint8_t *) rb;
    while (r8 < (uint8_t *) top) {
      gf_general_set_random(&g, w, 1);
      *r8a = g.w32;
      gf_general_set_random(&g, w, 0);
      *r8 = g.w32;
      r8a++;
      r8++;
    }
  } else {
    r32 = (uint32_t *) ra;
    for (i = 0; i < size/4; i++) r32[i] = MOA_Random_W(w, 1);
    r32 = (uint32_t *) rb;
    for (i = 0; i < size/4; i++) r32[i] = MOA_Random_W(w, 0);
  }
}

/* This sucks, but in order to time, you really need to avoid putting ifs in 
   the inner loops.  So, I'm doing a separate timing test for each w: 
   (4 & 8), 16, 32, 64, 128 and everything else.  Fortunately, the "everything else"
   tests can be equivalent to w=32.

   I'm also putting the results back into ra, because otherwise, the optimizer might
   figure out that we're not really doing anything in the inner loops and it 
   will chuck that. */

int gf_general_do_single_timing_test(gf_t *gf, void *ra, void *rb, int size, char test)
{
  gf_internal_t *h;
  void *top;
  uint8_t *r8a, *r8b, *top8;
  uint16_t *r16a, *r16b, *top16;
  uint32_t *r32a, *r32b, *top32;
  uint64_t *r64a, *r64b, *top64, *r64c;
  int w, rv;

  h = (gf_internal_t *) gf->scratch;
  w = h->w;
  top = (uint8_t *)ra + size;

  if (w == 8 || w == 4) {
    r8a = (uint8_t *) ra; 
    r8b = (uint8_t *) rb; 
    top8 = (uint8_t *) top;
    if (test == 'M') {
      while (r8a < top8) {
        *r8a = gf->multiply.w32(gf, *r8a, *r8b);
        r8a++;
        r8b++;
      }
    } else if (test == 'D') {
      while (r8a < top8) {
        *r8a = gf->divide.w32(gf, *r8a, *r8b);
        r8a++;
        r8b++;
      }
    } else if (test == 'I') {
      while (r8a < top8) {
        *r8a = gf->inverse.w32(gf, *r8a);
        r8a++;
      }
    }
    return (top8 - (uint8_t *) ra);
  }

  if (w == 16) {
    r16a = (uint16_t *) ra; 
    r16b = (uint16_t *) rb; 
    top16 = (uint16_t *) top;
    if (test == 'M') {
      while (r16a < top16) {
        *r16a = gf->multiply.w32(gf, *r16a, *r16b);
        r16a++;
        r16b++;
      }
    } else if (test == 'D') {
      while (r16a < top16) {
        *r16a = gf->divide.w32(gf, *r16a, *r16b);
        r16a++;
        r16b++;
      }
    } else if (test == 'I') {
      while (r16a < top16) {
        *r16a = gf->inverse.w32(gf, *r16a);
        r16a++;
      }
    }
    return (top16 - (uint16_t *) ra);
  }
  if (w <= 32) {
    r32a = (uint32_t *) ra; 
    r32b = (uint32_t *) rb; 
    top32 = (uint32_t *) ra + (size/4); /* This is for the "everything elses" */
    
    if (test == 'M') {
      while (r32a < top32) {
        *r32a = gf->multiply.w32(gf, *r32a, *r32b);
        r32a++;
        r32b++;
      }
    } else if (test == 'D') {
      while (r32a < top32) {
        *r32a = gf->divide.w32(gf, *r32a, *r32b);
        r32a++;
        r32b++;
      }
    } else if (test == 'I') {
      while (r32a < top32) {
        *r32a = gf->inverse.w32(gf, *r32a);
        r32a++;
      }
    }
    return (top32 - (uint32_t *) ra);
  }
  if (w == 64) {
    r64a = (uint64_t *) ra; 
    r64b = (uint64_t *) rb; 
    top64 = (uint64_t *) top;
    if (test == 'M') {
      while (r64a < top64) {
        *r64a = gf->multiply.w64(gf, *r64a, *r64b);
        r64a++;
        r64b++;
      }
    } else if (test == 'D') {
      while (r64a < top64) {
        *r64a = gf->divide.w64(gf, *r64a, *r64b);
        r64a++;
        r64b++;
      }
    } else if (test == 'I') {
      while (r64a < top64) {
        *r64a = gf->inverse.w64(gf, *r64a);
        r64a++;
      }
    }
    return (top64 - (uint64_t *) ra);
  }
  if (w == 128) {
    r64a = (uint64_t *) ra; 
    r64c = r64a;
    r64a += 2;
    r64b = (uint64_t *) rb; 
    top64 = (uint64_t *) top;
    rv = (top64 - r64a)/2;
    if (test == 'M') {
      while (r64a < top64) {
        gf->multiply.w128(gf, r64a, r64b, r64c);
        r64a += 2;
        r64b += 2;
      }
    } else if (test == 'D') {
      while (r64a < top64) {
        gf->divide.w128(gf, r64a, r64b, r64c);
        r64a += 2;
        r64b += 2;
      }
    } else if (test == 'I') {
      while (r64a < top64) {
        gf->inverse.w128(gf, r64a, r64c);
        r64a += 2;
      }
    }
    return rv;
  }
  return 0;
}
