%macro base64(
         OPERATION=ENCODE,   /* ENCODE or DECODE */
         FILEMODE=,          /* Create a new file (BLANK) or add to existing file (MOD) */
         INPUT=,             /* Input FILEREF or FILENAME */
         OUTPUT=             /* Output FILEREF or FILENAME */
         );
  %* Parameter Checks;
  %let OPERATION=%upcase(&OPERATION);
  %let FILEMODE=%upcase(&FILEMODE);
  %if %str(&OPERATION) NE %str(ENCODE) and %str(&OPERATION) NE %str(DECODE) %then %do;
    %put ERROR: The only operations supported is ENCODE or DECODE.;
    %put ERROR: You specified %trim(&OPERATION).;
    %return;
  %end; 
  %if %str(&FILEMODE) NE %str(MOD) %then %do;
    %if %str(&FILEMODE) NE %str( ) %then %do; 
      %put ERROR: FILEMODE %trim(&FILEMODE) not recognised;
      %return;
    %end;
  %end;

  %* Check INPUT for FILEREF or FILE;
  %if %length(&INPUT) < 9 %then %do;
    %if %sysfunc(fexist(&INPUT)) %then %do;
      %put NOTE: Identified Input "&INPUT" as a FILEFREF;
    %end;
    %else %do;
      %put ERROR: Invalid Input FILEREF - "&INPUT";
      %put ERROR: A raw filename must be longer than 8 characters;
      %return;
    %end;
  %end;
  %else %if %sysfunc(fileexist(&INPUT)) %then %do;
    %put NOTE: Identified Input "&INPUT" as a FILE;
    %let INPUT="&INPUT";
  %end;
  %else %do;
    %put ERROR: Input file does not exist - "%trim(&INPUT)";
    %return;
  %end;

  %* Check OUTPUT for FILEREF or FILE;
  %if %length(&OUTPUT) < 9 %then %do;
    %if %sysfunc(fexist(&OUTPUT)) %then %do;
      %put NOTE: Identified Output "&OUTPUT" as a FILEFREF;
    %end;
    %else %do;
      %put ERROR: Invalid Output FILEREF - "&OUTPUT";
      %put ERROR: A raw filename must be longer than 8 characters;
      %return;
    %end;
  %end;
  %else %do;
    %put NOTE: Output "&OUTPUT" treated as a FILE;
    %let OUTPUT="&OUTPUT";
  %end;

  %if &OPERATION = ENCODE %then %do;
    %put NOTE: Base64 Encoding Selected.;
    data _null_;
      length b64 $ 76 line $ 57;
      retain line "";

      /* Read one byte at a time from the input file */
      infile &INPUT recfm=F lrecl=1 end=eof;
      input @1 stream $char1.;

      /* Write Base64 with line length of 76 characters */
      file &OUTPUT lrecl=76 &FILEMODE;
      
      /* Place the current byte into the line buffer */
      substr(line,(_N_-(CEIL(_N_/57)-1)*57),1) = byte(rank(stream));

      /* 57 source bytes get base64 encoded to 76 bytes */ 
      if mod(_N_,57)=0 or EOF then do;
        /* Convert the buffer to base64 */
        if eof then b64=put(trim(line),$base64X76.);
        else b64=put(line,$base64X76.); 
        /* Write the base64 string out */
        put b64;
        /* Reset the buffer */
        line="";
      end;

    run;
  %end;
  %else %if &OPERATION = DECODE %then %do;
    %put NOTE: Base64 Decoding Selected;
    data _null_;
      length b64 $ 76 byte $ 1;
      /* We only deal with Base64 files with a max line length of 76 */
      infile &INPUT lrecl=76 truncover length=b64length;
      input @1 b64 $base64X76.;
      if _N_=1 then putlog "NOTE: Detected Base64 Line Length of " b64length;
      file &OUTPUT recfm=F lrecl=1;
      do i=1 to (b64length/4)*3;
        byte=byte(rank(substr(b64,i,1)));
        put byte $char1.;
      end;
    run;
  %end;

%mend;
