text: /*
   * %CopyrightBegin%
   *
   * Copyright Ericsson AB 2008-2018. All Rights Reserved.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   * %CopyrightEnd%
   */
  
  #ifdef HAVE_CONFIG_H
  #  include "config.h"
  #endif
  #include "sys.h"
  #include "erl_vm.h"
  #include "global.h"
  #include "erl_process.h"
  #include "error.h"
  #include "bif.h"
  #include "erl_binary.h"
  #include "big.h"
  #include "zlib.h"
  
  
  typedef void (*ChksumFun)(void *sum_in_out, unsigned char *buf, 
  			  unsigned buflen);
  
  /* Hidden trap target */
  static BIF_RETTYPE md5_2(BIF_ALIST_2);
  
  static Export *chksum_md5_2_exp;
  
  void erts_init_bif_chksum(void)
  {
      /* Non visual BIF to trap to. */
      erts_init_trap_export(&chksum_md5_2_exp,
  			  am_erlang, ERTS_MAKE_AM("md5_trap"), 2,
  			  &md5_2);
  }
      
  
  static Eterm do_chksum(ChksumFun sumfun, Process *p, Eterm ioterm, int left, 
  		       void *sum, int *res, int *err)
  {
      Eterm *objp;
      Eterm obj;
      int c;
      DECLARE_ESTACK(stack);
      unsigned char *bytes = NULL;
      int numbytes = 0;
  
      *err = 0;
      if (left <= 0 || is_nil(ioterm)) {
  	DESTROY_ESTACK(stack);
  	*res = 0;
  	return ioterm;
      }
      if(is_binary(ioterm)) {
  	Uint bitoffs;
  	Uint bitsize;
  	Uint size;
  	Eterm res_term = NIL;
  	unsigned char *bytes;
  	byte *temp_alloc = NULL;
  	
  	ERTS_GET_BINARY_BYTES(ioterm, bytes, bitoffs, bitsize);
  	if (bitsize != 0) {
  	    *res = 0;
  	    *err = 1;
  	    DESTROY_ESTACK(stack);
  	    return NIL;
  	}
  	if (bitoffs != 0) {
  	    bytes = erts_get_aligned_binary_bytes(ioterm, &temp_alloc);
  	    /* The call to erts_get_aligned_binary_bytes cannot fail as 
  	       we'we already checked bitsize and that this is a binary */
  	}
  
  	size = binary_size(ioterm);
  
  
  	if (size > left) {
  	    Eterm *hp;
  	    ErlSubBin *sb;
  	    Eterm orig;
  	    Uint offset;
  	    /* Split the binary in two parts, of which we 
  	       only process the first */
  	    hp = HAlloc(p, ERL_SUB_BIN_SIZE);
  	    sb = (ErlSubBin *) hp;
  	    ERTS_GET_REAL_BIN(ioterm, orig, offset, bitoffs, bitsize);
  	    sb->thing_word = HEADER_SUB_BIN;
  	    sb->size = size - left;
  	    sb->offs = offset + left;
  	    sb->orig = orig;
  	    sb->bitoffs = bitoffs;
  	    sb->bitsize = bitsize;
  	    sb->is_writable = 0;
  	    res_term = make_binary(sb);
  	    size = left;
  	}
  	(*sumfun)(sum, bytes, size);
  	*res = size;
  	DESTROY_ESTACK(stack);
  	erts_free_aligned_binary_bytes(temp_alloc);
  	return res_term;
      }
  	
      if (!is_list(ioterm)) {
  	*res = 0;
  	*err = 1;
  	DESTROY_ESTACK(stack);
  	return NIL;
      }
  
      /* OK a list, needs to be processed in order, handling each flat list-level
         as they occur, just like io_list_to_binary would */
      *res = 0;
      ESTACK_PUSH(stack,ioterm);
      while (!ESTACK_ISEMPTY(stack) && left) {
  	ioterm = ESTACK_POP(stack);
  	if (is_nil(ioterm)) {
  	    /* ignore empty lists */
  	    continue;
  	}
  	if(is_list(ioterm)) {
  L_Again:   /* Restart with sublist, old listend was pushed on stack */
  	    objp = list_val(ioterm);
  	    obj = CAR(objp);
  	    for(;;) { /* loop over one flat list of bytes and binaries
  		         until sublist or list end is encountered */
  		if (is_byte(obj)) {
  		    int bsize = 0;
  		    for(;;) {
  			if (bsize >= numbytes) {
  			    if (!bytes) {
  				bytes = erts_alloc(ERTS_ALC_T_TMP, 
  						   numbytes = 500);
  			    } else {
  				if (numbytes > left) {
  				    numbytes += left;
  				} else {
  				    numbytes *= 2;
  				}
  				bytes = erts_realloc(ERTS_ALC_T_TMP, bytes,
  						     numbytes);
  			    }
  			}  
  			bytes[bsize++] = (unsigned char) unsigned_val(obj);
  			--left;
  			ioterm = CDR(objp);
  			if (!is_list(ioterm)) {
  			    break;
  			}
  			objp = list_val(ioterm);
  			obj = CAR(objp);
  			if (!is_byte(obj))
  			    break;
  			if (!left) {
  			    break;
  			}
  		    }
  		    (*sumfun)(sum, bytes, bsize);
  		    *res += bsize;
  		} else if (is_nil(obj)) {
  		    ioterm = CDR(objp);
  		    if (!is_list(ioterm)) {
  			break;
  		    }
  		    objp = list_val(ioterm);
  		    obj = CAR(objp);
  		} else if (is_list(obj)) {
  		    /* push rest of list for later processing, start 
  		       again with sublist */
  		    ESTACK_PUSH(stack,CDR(objp));
  		    ioterm = obj;
  		    goto L_Again;
  		} else if (is_binary(obj)) {
  		    int sres, serr;
  		    Eterm rest_term;
  		    rest_term = do_chksum(sumfun, p, obj, left, sum, &sres,
  					  &serr);
  		    *res += sres;
  		    if (serr != 0) {
  			*err = 1;
  			DESTROY_ESTACK(stack);
  			if (bytes != NULL)
  			    erts_free(ERTS_ALC_T_TMP, bytes);
  			return NIL;
  		    }
  		    left -= sres;
  		    if (rest_term != NIL) {
  			Eterm *hp;
  			hp = HAlloc(p, 2);
  			obj = CDR(objp);
  			ioterm = CONS(hp, rest_term, obj);
  			left = 0;
  			break;
  		    }
  		    ioterm = CDR(objp);
  		    if (is_list(ioterm)) {
  			/* objp and obj need to be updated if 
  			   loop is to continue */
  			objp = list_val(ioterm);
  			obj = CAR(objp);
  		    }
  		} else {
  		    *err = 1;
  		    DESTROY_ESTACK(stack);
  		    if (bytes != NULL)
  			erts_free(ERTS_ALC_T_TMP, bytes);
  		    return NIL;
  		} 
  		if (!left || is_nil(ioterm) || !is_list(ioterm)) {
  		    break;
  		}
  	    } /* for(;;) */
  	} /* is_list(ioterm) */
  
  	if (!left) {
  #ifdef ALLOW_BYTE_TAIL
  	    if (is_byte(ioterm)) {
  		/* inproper list with byte tail*/
  		Eterm *hp;
  		hp = HAlloc(p, 2);
  		ioterm = CONS(hp, ioterm, NIL);
  	    }
  #else
  	    ;
  #endif
  	} else if (!is_list(ioterm) && !is_nil(ioterm)) {
  	    /* inproper list end */
  #ifdef ALLOW_BYTE_TAIL
  	    if (is_byte(ioterm)) {
  		unsigned char b[1];
  		b[0] = (unsigned char) unsigned_val(ioterm);
  		(*sumfun)(sum, b, 1);
  		++(*res);
  		--left;
  		ioterm = NIL;
  	    } else
  #endif 
  	    if is_binary(ioterm) {
  		int sres, serr;
  		ioterm = do_chksum(sumfun, p, ioterm, left, sum, &sres, &serr);
  		*res +=sres;
  		if (serr != 0) {
  		    *err = 1;
  		    DESTROY_ESTACK(stack);
  		    if (bytes != NULL)
  			erts_free(ERTS_ALC_T_TMP, bytes);
  		    return NIL;
  		}
  		left -= sres;
  	    } else {
  		*err = 1;
  		DESTROY_ESTACK(stack);
  		if (bytes != NULL)
  		    erts_free(ERTS_ALC_T_TMP, bytes);
  		return NIL;
  	    }
  	}
      } /* while left and not estack empty */
      c = ESTACK_COUNT(stack);
      if (c > 0) {
  	Eterm *hp = HAlloc(p,2*c);
  	while(!ESTACK_ISEMPTY(stack)) {
  	    Eterm st = ESTACK_POP(stack);
  	    ioterm = CONS(hp, ioterm, st);
  	    hp += 2;
  	}
      }
      DESTROY_ESTACK(stack);
      if (bytes != NULL)
  	erts_free(ERTS_ALC_T_TMP, bytes);
      return ioterm;
  }
  
  static void adler32_wrap(void *vsum, unsigned char *buf, unsigned buflen)
  {
      unsigned long sum = *((unsigned long *) vsum);
      sum = adler32(sum,buf,buflen);
      *((unsigned long *) vsum) = sum;
  }
  
  static void crc32_wrap(void *vsum, unsigned char *buf, unsigned buflen)
  {
      unsigned long sum = *((unsigned long *) vsum);
      sum = crc32(sum,buf,buflen);
      *((unsigned long *) vsum) = sum;
  }
  
  static void md5_wrap(void *vsum, unsigned char *buf, unsigned buflen)
  {
      MD5_CTX *ctx = ((MD5_CTX *) vsum);
      MD5Update(ctx,buf,buflen);
  }
  
  #define BYTES_PER_REDUCTION 10
  #define CHUNK_PER_SCHEDULE (BYTES_PER_REDUCTION * CONTEXT_REDS)
  
  BIF_RETTYPE
  crc32_1(BIF_ALIST_1)
  {
      unsigned long chksum;
      int res, err;
      Eterm rest,res_sum;
      chksum = crc32(0,NULL,0);
  
      rest = do_chksum(&crc32_wrap,BIF_P,BIF_ARG_1,CHUNK_PER_SCHEDULE,
  		     (void *) &chksum,&res,
  		     &err);
      BUMP_REDS(BIF_P,res / BYTES_PER_REDUCTION);
      if (err != 0) {
  	BIF_ERROR(BIF_P, BADARG);
      }
      res_sum = erts_make_integer(chksum,BIF_P);
      if (rest != NIL) {
  	BUMP_ALL_REDS(BIF_P);
  	BIF_TRAP2(BIF_TRAP_EXPORT(BIF_crc32_2), BIF_P, res_sum, rest);
      }
      BIF_RET(res_sum);
  }
  
  BIF_RETTYPE
  crc32_2(BIF_ALIST_2)
  {
      unsigned long chksum;
      int res, err;
      Eterm rest,res_sum;
      Uint u;
      if (!term_to_Uint(BIF_ARG_1, &u) || ((u >> 16) >> 16) != 0) {
  	BIF_ERROR(BIF_P, BADARG);
      }
      chksum = (unsigned long) u;
  
      rest = do_chksum(&crc32_wrap,BIF_P,BIF_ARG_2,CHUNK_PER_SCHEDULE,
  		     (void *) &chksum,&res,
  		     &err);
      BUMP_REDS(BIF_P,res / BYTES_PER_REDUCTION);
      if (err != 0) {
  	BIF_ERROR(BIF_P, BADARG);
      }
      res_sum = erts_make_integer(chksum,BIF_P);
      if (rest != NIL) {
  	BUMP_ALL_REDS(BIF_P);
  	BIF_TRAP2(BIF_TRAP_EXPORT(BIF_crc32_2), BIF_P, res_sum, rest);
      }
      BIF_RET(res_sum);
  }
  
  BIF_RETTYPE
  crc32_combine_3(BIF_ALIST_3)
  {
      unsigned long chksum1,chksum2;
      z_off_t length;
      Uint32 res;
      Eterm res_sum;
      Uint u;
  
      if (!term_to_Uint(BIF_ARG_1, &u) || ((u >> 16) >> 16) != 0) {
  	BIF_ERROR(BIF_P, BADARG);
      }
      chksum1 = (unsigned long) u;
  
      if (!term_to_Uint(BIF_ARG_2, &u) || ((u >> 16) >> 16) != 0) {
  	BIF_ERROR(BIF_P, BADARG);
      }
      chksum2 = (unsigned long) u;
  
      if (!term_to_Uint(BIF_ARG_3, &u) || ((u >> 16) >> 16) != 0) {
  	BIF_ERROR(BIF_P, BADARG);
      }
      length = (z_off_t) u;
  
      res = (Uint32) crc32_combine(chksum1,chksum2,length);
  
      res_sum = erts_make_integer(res,BIF_P);
      BIF_RET(res_sum);
  }
  
  BIF_RETTYPE
  adler32_1(BIF_ALIST_1)
  {
      unsigned long chksum;
      int res, err;
      Eterm rest,res_sum;
      chksum = adler32(0,NULL,0);
  
      rest = do_chksum(&adler32_wrap,BIF_P,BIF_ARG_1,CHUNK_PER_SCHEDULE,
  		     (void *) &chksum,&res,
  		     &err);
      BUMP_REDS(BIF_P,res / BYTES_PER_REDUCTION);
      if (err != 0) {
  	BIF_ERROR(BIF_P, BADARG);
      }
      res_sum = erts_make_integer(chksum,BIF_P);
      if (rest != NIL) {
  	BUMP_ALL_REDS(BIF_P);
  	BIF_TRAP2(BIF_TRAP_EXPORT(BIF_adler32_2), BIF_P, res_sum, rest);
      }
      BIF_RET(res_sum);
  }
  
  BIF_RETTYPE
  adler32_2(BIF_ALIST_2)
  {
      unsigned long chksum;
      int res, err;
      Eterm rest,res_sum;
      Uint u;
      if (!term_to_Uint(BIF_ARG_1, &u) || ((u >> 16) >> 16) != 0) {
  	BIF_ERROR(BIF_P, BADARG);
      }
      chksum = (unsigned long) u;
  
      rest = do_chksum(&adler32_wrap,BIF_P,BIF_ARG_2,CHUNK_PER_SCHEDULE,
  		     (void *) &chksum,&res,
  		     &err);
      BUMP_REDS(BIF_P,res / BYTES_PER_REDUCTION);
      if (err != 0) {
  	BIF_ERROR(BIF_P, BADARG);
      }
      res_sum = erts_make_integer(chksum,BIF_P);
      if (rest != NIL) {
  	BUMP_ALL_REDS(BIF_P);
  	BIF_TRAP2(BIF_TRAP_EXPORT(BIF_adler32_2), BIF_P, res_sum, rest);
      }
      BIF_RET(res_sum);
  }
  
  BIF_RETTYPE
  adler32_combine_3(BIF_ALIST_3)
  {
      unsigned long chksum1,chksum2;
      z_off_t length;
      Uint32 res;
      Eterm res_sum;
      Uint u;
  
      if (!term_to_Uint(BIF_ARG_1, &u) || ((u >> 16) >> 16) != 0) {
  	BIF_ERROR(BIF_P, BADARG);
      }
      chksum1 = (unsigned long) u;
  
      if (!term_to_Uint(BIF_ARG_2, &u) || ((u >> 16) >> 16) != 0) {
  	BIF_ERROR(BIF_P, BADARG);
      }
      chksum2 = (unsigned long) u;
  
      if (!term_to_Uint(BIF_ARG_3, &u) || ((u >> 16) >> 16) != 0) {
  	BIF_ERROR(BIF_P, BADARG);
      }
      length = (z_off_t) u;
  
      if (length == 0) { /* Workaround for unexpected behaviour in zlib. */
  	res = (Uint32) chksum1;
      } else {
  	res = (Uint32) adler32_combine(chksum1,chksum2,length);
      }
  
      res_sum = erts_make_integer(res,BIF_P);
      BIF_RET(res_sum);
  }
  
  
  BIF_RETTYPE
  md5_1(BIF_ALIST_1)
  {
      Eterm bin;
      byte* bytes;
      Eterm rest;
      int res, err;
  
      MD5_CTX context;
      MD5Init(&context);
      
      rest = do_chksum(&md5_wrap,BIF_P,BIF_ARG_1,100,(void *) &context,&res,
  		     &err);
      if (err != 0) {
  	BUMP_REDS(BIF_P,res);
  	BIF_ERROR(BIF_P, BADARG);
      }
      if (rest != NIL) {
  	BUMP_ALL_REDS(BIF_P);
  	 bin = new_binary(BIF_P, (byte *) &context, sizeof(MD5_CTX));
  	 BIF_TRAP2(chksum_md5_2_exp, BIF_P, bin, rest);
      }
      BUMP_REDS(BIF_P,res);
      bin = new_binary(BIF_P, (byte *)NULL, 16);
      bytes = binary_bytes(bin);
      MD5Final(bytes, &context);
      BIF_RET(bin);
  }
  
  /* Hidden trap target */
  static BIF_RETTYPE
  md5_2(BIF_ALIST_2)
  {
      byte *bytes;
      MD5_CTX context;
      Eterm rest;
      Eterm bin;
      int res, err;
  
      /* No need to check context, this function cannot be called with unaligned
         or badly sized context as it's always trapped to. */
      bytes = binary_bytes(BIF_ARG_1);
      sys_memcpy(&context,bytes,sizeof(MD5_CTX));
      rest = do_chksum(&md5_wrap,BIF_P,BIF_ARG_2,100,(void *) &context,&res,
  		     &err);
      if (err != 0) {
  	BUMP_REDS(BIF_P,res);
  	BIF_ERROR(BIF_P, BADARG);
      }
      if (rest != NIL) {
  	BUMP_ALL_REDS(BIF_P);
  	bin = new_binary(BIF_P, (byte *) &context, sizeof(MD5_CTX));
  	BIF_TRAP2(chksum_md5_2_exp, BIF_P, bin, rest);
      }
      BUMP_REDS(BIF_P,res);
      bin = new_binary(BIF_P, (byte *)NULL, 16);
      bytes = binary_bytes(bin);
      MD5Final(bytes, &context);
      BIF_RET(bin);
  }
  
  BIF_RETTYPE
  md5_init_0(BIF_ALIST_0)
  {
      Eterm bin;
      byte* bytes;
  
      bin = erts_new_heap_binary(BIF_P, (byte *)NULL, sizeof(MD5_CTX), &bytes);
      MD5Init((MD5_CTX *)bytes);
      BIF_RET(bin);
  }
  
  BIF_RETTYPE
  md5_update_2(BIF_ALIST_2)
  {
      byte *bytes;
      MD5_CTX context;
      Eterm rest;
      Eterm bin;
      int res, err;
      byte *temp_alloc = NULL;
  
      if ((bytes = erts_get_aligned_binary_bytes(BIF_ARG_1, &temp_alloc)) == NULL) {
  	erts_free_aligned_binary_bytes(temp_alloc);
  	BIF_ERROR(BIF_P, BADARG);
      }
      if (binary_size(BIF_ARG_1) != sizeof(MD5_CTX)) {
  	erts_free_aligned_binary_bytes(temp_alloc);
  	BIF_ERROR(BIF_P, BADARG);
      }
      sys_memcpy(&context,bytes,sizeof(MD5_CTX));
      erts_free_aligned_binary_bytes(temp_alloc);
      rest = do_chksum(&md5_wrap,BIF_P,BIF_ARG_2,100,(void *) &context,&res,
  		     &err);
      if (err != 0) {
  	BUMP_REDS(BIF_P,res);
  	BIF_ERROR(BIF_P, BADARG);
      }
      bin = new_binary(BIF_P, (byte *) &context, sizeof(MD5_CTX));
      if (rest != NIL) {
  	BUMP_ALL_REDS(BIF_P);
  	BIF_TRAP2(BIF_TRAP_EXPORT(BIF_md5_update_2), BIF_P, bin, rest);
      }
      BUMP_REDS(BIF_P,res);
      BIF_RET(bin);
  }
  
  BIF_RETTYPE
  md5_final_1(BIF_ALIST_1)
  {
      Eterm bin;
      byte* context;
      byte* result;
      MD5_CTX ctx_copy;
      byte* temp_alloc = NULL;
  
      if ((context = erts_get_aligned_binary_bytes(BIF_ARG_1, &temp_alloc)) == NULL) {
      error:
  	erts_free_aligned_binary_bytes(temp_alloc);
  	BIF_ERROR(BIF_P, BADARG);
      }
      if (binary_size(BIF_ARG_1) != sizeof(MD5_CTX)) {
  	goto error;
      }
      bin = erts_new_heap_binary(BIF_P, (byte *)NULL, 16, &result);
      sys_memcpy(&ctx_copy, context, sizeof(MD5_CTX));
      erts_free_aligned_binary_bytes(temp_alloc);
      MD5Final(result, &ctx_copy);
      BIF_RET(bin);
  }
  