github.com/aldelo/common@v1.5.1/ascii/ascii.go (about)

     1  package ascii
     2  
     3  /*
     4   * Copyright 2020-2023 Aldelo, LP
     5   *
     6   * Licensed under the Apache License, Version 2.0 (the "License");
     7   * you may not use this file except in compliance with the License.
     8   * You may obtain a copy of the License at
     9   *
    10   *     http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   * Unless required by applicable law or agreed to in writing, software
    13   * distributed under the License is distributed on an "AS IS" BASIS,
    14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15   * See the License for the specific language governing permissions and
    16   * limitations under the License.
    17   */
    18  
    19  import (
    20  	"fmt"
    21  	"strconv"
    22  	"strings"
    23  )
    24  
    25  // ascii definition
    26  // use string(...) to convert the const into string value
    27  const (
    28  	NUL   = 0x00 // '\0' Null
    29  	SOH   = 0x01 //      Start of Header
    30  	STX   = 0x02 //      Start of Text
    31  	ETX   = 0x03 //      End of Text
    32  	EOT   = 0x04 //      End of Transmission
    33  	ENQ   = 0x05 //      Enquiry
    34  	ACK   = 0x06 //      Acknowledgement
    35  	BEL   = 0x07 // '\a' Bell
    36  	BS    = 0x08 // '\b' Backspace
    37  	HT    = 0x09 // '\t' Horizontal Tab
    38  	LF    = 0x0A // '\n' Line Feed
    39  	VT    = 0x0B // '\v' Vertical Tab
    40  	FF    = 0x0C // '\f' Form Feed
    41  	CR    = 0x0D // '\r' Carriage Return
    42  	SO    = 0x0E //      Shift Out
    43  	SI    = 0x0F //      Shift In
    44  	DLE   = 0x10 //      Device Idle
    45  	DC1   = 0x11 //      Device Control 1
    46  	DC2   = 0x12 //      Device Control 2
    47  	DC3   = 0x13 //      Device Control 3
    48  	DC4   = 0x14 //      Device Control 4
    49  	NAK   = 0x15 //      Negative Ack
    50  	SYN   = 0x16 //      Synchronize
    51  	ETB   = 0x17 //      End of Transmission Block
    52  	CAN   = 0x18 //      Cancel
    53  	EM    = 0x19 //      End of Medium
    54  	SUB   = 0x1A //      Substitute
    55  	ESC   = 0x1B // '\e' Escape
    56  	FS    = 0x1C //      Field Separator
    57  	GS    = 0x1D //      Group Separator
    58  	RS    = 0x1E //      Record Separator
    59  	US    = 0x1F //      Unit Separator
    60  	SP    = 0x20 //      Space
    61  	DEL   = 0x7F //      Delete
    62  	COMMA = 0x2C // Comma
    63  	COLON = 0x3A // Colon
    64  	PIPE  = 0x7C // Pipe
    65  
    66  )
    67  
    68  func AsciiToString(i int) string {
    69  	return string(rune(i))
    70  }
    71  
    72  // GetLRC calculates the LRC value for input string,
    73  // returns blank LRC to indicate error condition (see error for reason)
    74  //
    75  // parameters:
    76  //
    77  //	data = includes the STX and ETX but not LRC if exists
    78  //
    79  // returns:
    80  //
    81  //	string = LRC string value
    82  func GetLRC(data string) (string, error) {
    83  	if len(strings.Trim(data, " ")) <= 1 {
    84  		return "", fmt.Errorf("Data is Required for LRC Calculation")
    85  	}
    86  
    87  	// LRC check excludes STX
    88  	firstChar := data[:1]
    89  
    90  	if firstChar == AsciiToString(STX) {
    91  		data = data[1:]
    92  	}
    93  
    94  	// excluding STX, must be 2 or more chars
    95  	if len(data) < 2 {
    96  		return "", fmt.Errorf("Data Must Be 2 Characters or More for LRC Calculation")
    97  	}
    98  
    99  	lrcBytes := []byte(data)
   100  	lrc := byte(0)
   101  
   102  	// loop through each element, XOR product of element and next adjacent element and continue
   103  	for i, v := range lrcBytes {
   104  		if i == 0 {
   105  			lrc = v
   106  		} else {
   107  			lrc ^= v
   108  		}
   109  	}
   110  
   111  	// return lrc value
   112  	return string(lrc), nil
   113  }
   114  
   115  // IsLRCValid checks if the input data that contains the entire string, including STX ETX and LRC, that its LRC is valid for the content of the data
   116  func IsLRCValid(data string) bool {
   117  	if len(data) <= 2 {
   118  		return false
   119  	}
   120  
   121  	if calcLrc, err := GetLRC(data[:len(data)-1]); err != nil || len(calcLrc) == 0 {
   122  		return false
   123  	} else {
   124  		return data[len(data)-1:] == calcLrc
   125  	}
   126  }
   127  
   128  // IsCreditCardMod10Valid performs modulo 10 credit card number validation
   129  func IsCreditCardMod10Valid(cardNumber string) (bool, error) {
   130  	cardNumber = strings.Trim(cardNumber, " ")
   131  
   132  	if len(cardNumber) < 5 {
   133  		return false, fmt.Errorf("Card Number Must Be Greater or Equal to 5 Digits")
   134  	}
   135  
   136  	if _, err := strconv.ParseUint(cardNumber, 10, 64); err != nil {
   137  		return false, fmt.Errorf("Card Number Must Be Numeric")
   138  	}
   139  
   140  	source := cardNumber[:len(cardNumber)-1]
   141  	checkDigit := cardNumber[len(cardNumber)-1:]
   142  
   143  	result := 0
   144  	multiplier := 2
   145  
   146  	// loop through each element from right to left,
   147  	// multiple element by value of 2, and 1 alternating
   148  	for i := len(source) - 1; i >= 0; i-- {
   149  		if temp, e := strconv.Atoi(string(source[i])); e == nil {
   150  			temp *= multiplier
   151  
   152  			if multiplier == 2 {
   153  				multiplier = 1
   154  			} else {
   155  				multiplier = 2
   156  			}
   157  
   158  			for {
   159  				if temp < 10 {
   160  					result += temp
   161  					break
   162  				}
   163  
   164  				buf := strconv.Itoa(temp)
   165  				x, _ := strconv.Atoi(buf[:1])
   166  				y, _ := strconv.Atoi(buf[len(buf)-1:])
   167  				temp = x + y
   168  			}
   169  		}
   170  	}
   171  
   172  	// find the next highest multiple of 10
   173  	multiplier = result % 10
   174  
   175  	if multiplier > 0 {
   176  		multiplier = result/10 + 1
   177  	} else {
   178  		multiplier = result / 10
   179  	}
   180  
   181  	// get check digit
   182  	result = multiplier*10 - result
   183  
   184  	if chk, err := strconv.Atoi(checkDigit); err != nil {
   185  		return false, fmt.Errorf("Convert Check Digit Failed: %s", err)
   186  	} else {
   187  		return chk == result, nil
   188  	}
   189  }
   190  
   191  // EnvelopWithStxEtxLrc will take content data, wrap with STX, ETX, and calculate LRC to append
   192  //
   193  // contentData = do not include STX, ETX, LRC
   194  func EnvelopWithStxEtxLrc(contentData string) string {
   195  	if len(contentData) == 0 {
   196  		return ""
   197  	}
   198  
   199  	if contentData[:1] != AsciiToString(STX) {
   200  		contentData = AsciiToString(STX) + contentData
   201  	}
   202  
   203  	if len(contentData) >= 2 {
   204  		removeLast := false
   205  		d := contentData[:2]
   206  
   207  		if d[:1] == AsciiToString(ETX) {
   208  			removeLast = true
   209  		} else if d[len(d)-1:] != AsciiToString(ETX) {
   210  			contentData += AsciiToString(ETX)
   211  		}
   212  
   213  		if removeLast {
   214  			contentData = contentData[:len(contentData)-1]
   215  		}
   216  	} else {
   217  		contentData += AsciiToString(ETX)
   218  	}
   219  
   220  	lrc, _ := GetLRC(contentData)
   221  	return contentData + lrc
   222  }
   223  
   224  // StripStxEtxLrcFromEnvelop removes STX ETX and LRC from envelopment and returns content data.
   225  // this method will validate LRC, if LRC fails, blank is returned.
   226  //
   227  // ignoreValueToLeftOfDelimiterSymbol = if set, any value to left side of this delimiter (along with this delimiter) is excluded from the return result
   228  func StripStxEtxLrcFromEnvelop(envelopData string, ignoreValueToLeftOfDelimiterSymbol ...string) string {
   229  	if len(envelopData) == 0 {
   230  		return ""
   231  	}
   232  
   233  	if ok := IsLRCValid(envelopData); ok {
   234  		// remove lrc
   235  		envelopData = envelopData[:len(envelopData)-1]
   236  
   237  		if len(envelopData) == 0 {
   238  			return ""
   239  		}
   240  
   241  		// remove stx
   242  		if envelopData[:1] == AsciiToString(STX) {
   243  			if len(envelopData) > 1 {
   244  				envelopData = envelopData[1:]
   245  			} else {
   246  				return ""
   247  			}
   248  		}
   249  
   250  		// remove etx
   251  		if envelopData[len(envelopData)-1:] == AsciiToString(ETX) {
   252  			if len(envelopData) > 1 {
   253  				envelopData = envelopData[:len(envelopData)-1]
   254  			} else {
   255  				return ""
   256  			}
   257  		}
   258  
   259  		// if value to left of delimiter is to be excluded
   260  		if len(ignoreValueToLeftOfDelimiterSymbol) > 0 {
   261  			if p := strings.SplitN(envelopData, ignoreValueToLeftOfDelimiterSymbol[0], 2); len(p) == 2 {
   262  				envelopData = p[1]
   263  			}
   264  		}
   265  
   266  		return envelopData
   267  	} else {
   268  		return ""
   269  	}
   270  }
   271  
   272  // ControlCharToWord converts non-printable control char to word
   273  func ControlCharToWord(data string) string {
   274  	data = strings.ReplaceAll(data, AsciiToString(STX), "[STX]")
   275  	data = strings.ReplaceAll(data, AsciiToString(ETX), "[ETX]")
   276  	data = strings.ReplaceAll(data, AsciiToString(ETB), "[ETB]")
   277  	data = strings.ReplaceAll(data, AsciiToString(ACK), "[ACK]")
   278  	data = strings.ReplaceAll(data, AsciiToString(NAK), "[NAK]")
   279  	data = strings.ReplaceAll(data, AsciiToString(ENQ), "[ENQ]")
   280  	data = strings.ReplaceAll(data, AsciiToString(DLE), "[DLE]")
   281  	data = strings.ReplaceAll(data, AsciiToString(DC1), "[DC1]")
   282  	data = strings.ReplaceAll(data, AsciiToString(DC2), "[DC2]")
   283  	data = strings.ReplaceAll(data, AsciiToString(DC3), "[DC3]")
   284  	data = strings.ReplaceAll(data, AsciiToString(DC4), "[DC4]")
   285  	data = strings.ReplaceAll(data, AsciiToString(FS), "[FS]")
   286  	data = strings.ReplaceAll(data, AsciiToString(US), "[US]")
   287  	data = strings.ReplaceAll(data, AsciiToString(GS), "[GS]")
   288  	data = strings.ReplaceAll(data, AsciiToString(RS), "[RS]")
   289  	data = strings.ReplaceAll(data, AsciiToString(BS), "[BS]")
   290  	data = strings.ReplaceAll(data, AsciiToString(BEL), "[BEL]")
   291  	data = strings.ReplaceAll(data, AsciiToString(DEL), "[DEL]")
   292  	data = strings.ReplaceAll(data, AsciiToString(EOT), "[EOT]")
   293  	data = strings.ReplaceAll(data, AsciiToString(COMMA), "[COMMA]")
   294  	data = strings.ReplaceAll(data, AsciiToString(COLON), "[COLON]")
   295  	data = strings.ReplaceAll(data, AsciiToString(PIPE), "[PIPE]")
   296  	data = strings.ReplaceAll(data, AsciiToString(NUL), "[NULL]")
   297  	data = strings.ReplaceAll(data, AsciiToString(SOH), "[SOH]")
   298  	data = strings.ReplaceAll(data, AsciiToString(HT), "[HT]")
   299  	data = strings.ReplaceAll(data, AsciiToString(LF), "[LF]")
   300  	data = strings.ReplaceAll(data, AsciiToString(VT), "[VT]")
   301  	data = strings.ReplaceAll(data, AsciiToString(FF), "[FF]")
   302  	data = strings.ReplaceAll(data, AsciiToString(CR), "[CR]")
   303  	data = strings.ReplaceAll(data, AsciiToString(SO), "[SO]")
   304  	data = strings.ReplaceAll(data, AsciiToString(SI), "[SI]")
   305  	data = strings.ReplaceAll(data, AsciiToString(SP), "[SP]")
   306  	data = strings.ReplaceAll(data, AsciiToString(SYN), "[SYN]")
   307  	data = strings.ReplaceAll(data, AsciiToString(CAN), "[CAN]")
   308  	data = strings.ReplaceAll(data, AsciiToString(EM), "[EM]")
   309  	data = strings.ReplaceAll(data, AsciiToString(SUB), "[SUB]")
   310  	data = strings.ReplaceAll(data, AsciiToString(ESC), "[ESC]")
   311  
   312  	return data
   313  }
   314  
   315  // ControlCharToASCII converts non-printable control char represented in word to ascii non-printable form
   316  func ControlCharToASCII(data string) string {
   317  	data = strings.ReplaceAll(data, "[STX]", AsciiToString(STX))
   318  	data = strings.ReplaceAll(data, "[ETX]", AsciiToString(ETX))
   319  	data = strings.ReplaceAll(data, "[ETB]", AsciiToString(ETB))
   320  	data = strings.ReplaceAll(data, "[ACK]", AsciiToString(ACK))
   321  	data = strings.ReplaceAll(data, "[NAK]", AsciiToString(NAK))
   322  	data = strings.ReplaceAll(data, "[ENQ]", AsciiToString(ENQ))
   323  	data = strings.ReplaceAll(data, "[DLE]", AsciiToString(DLE))
   324  	data = strings.ReplaceAll(data, "[DC1]", AsciiToString(DC1))
   325  	data = strings.ReplaceAll(data, "[DC2]", AsciiToString(DC2))
   326  	data = strings.ReplaceAll(data, "[DC3]", AsciiToString(DC3))
   327  	data = strings.ReplaceAll(data, "[DC4]", AsciiToString(DC4))
   328  	data = strings.ReplaceAll(data, "[FS]", AsciiToString(FS))
   329  	data = strings.ReplaceAll(data, "[US]", AsciiToString(US))
   330  	data = strings.ReplaceAll(data, "[GS]", AsciiToString(GS))
   331  	data = strings.ReplaceAll(data, "[RS]", AsciiToString(RS))
   332  	data = strings.ReplaceAll(data, "[BS]", AsciiToString(BS))
   333  	data = strings.ReplaceAll(data, "[BEL]", AsciiToString(BEL))
   334  	data = strings.ReplaceAll(data, "[DEL]", AsciiToString(DEL))
   335  	data = strings.ReplaceAll(data, "[EOT]", AsciiToString(EOT))
   336  	data = strings.ReplaceAll(data, "[COMMA]", AsciiToString(COMMA))
   337  	data = strings.ReplaceAll(data, "[COLON]", AsciiToString(COLON))
   338  	data = strings.ReplaceAll(data, "[PIPE]", AsciiToString(PIPE))
   339  	data = strings.ReplaceAll(data, "[NULL]", AsciiToString(NUL))
   340  	data = strings.ReplaceAll(data, "[SOH]", AsciiToString(SOH))
   341  	data = strings.ReplaceAll(data, "[HT]", AsciiToString(HT))
   342  	data = strings.ReplaceAll(data, "[LF]", AsciiToString(LF))
   343  	data = strings.ReplaceAll(data, "[VT]", AsciiToString(VT))
   344  	data = strings.ReplaceAll(data, "[FF]", AsciiToString(FF))
   345  	data = strings.ReplaceAll(data, "[CR]", AsciiToString(CR))
   346  	data = strings.ReplaceAll(data, "[SO]", AsciiToString(SO))
   347  	data = strings.ReplaceAll(data, "[SI]", AsciiToString(SI))
   348  	data = strings.ReplaceAll(data, "[SP]", AsciiToString(SP))
   349  	data = strings.ReplaceAll(data, "[SYN]", AsciiToString(SYN))
   350  	data = strings.ReplaceAll(data, "[CAN]", AsciiToString(CAN))
   351  	data = strings.ReplaceAll(data, "[EM]", AsciiToString(EM))
   352  	data = strings.ReplaceAll(data, "[SUB]", AsciiToString(SUB))
   353  	data = strings.ReplaceAll(data, "[ESC]", AsciiToString(ESC))
   354  
   355  	return data
   356  }
   357  
   358  // EscapeNonPrintable converts non printable \x00 - \x1f to pseudo escaped format
   359  func EscapeNonPrintable(data string) string {
   360  	data = strings.Replace(data, AsciiToString(NUL), "[NUL_00]", -1)
   361  	data = strings.Replace(data, AsciiToString(SOH), "[SOH_01]", -1)
   362  	data = strings.Replace(data, AsciiToString(STX), "[STX_02]", -1)
   363  	data = strings.Replace(data, AsciiToString(ETX), "[ETX_03]", -1)
   364  	data = strings.Replace(data, AsciiToString(EOT), "[EOT_04]", -1)
   365  	data = strings.Replace(data, AsciiToString(ENQ), "[ENQ_05]", -1)
   366  	data = strings.Replace(data, AsciiToString(ACK), "[ACK_06]", -1)
   367  	data = strings.Replace(data, AsciiToString(BEL), "[BEL_07]", -1)
   368  	data = strings.Replace(data, AsciiToString(BS), "[BS_08]", -1)
   369  	data = strings.Replace(data, AsciiToString(HT), "[HT_09]", -1)
   370  	data = strings.Replace(data, AsciiToString(LF), "[LF_0A]", -1)
   371  	data = strings.Replace(data, AsciiToString(VT), "[VT_0B]", -1)
   372  	data = strings.Replace(data, AsciiToString(FF), "[FF_0C]", -1)
   373  	data = strings.Replace(data, AsciiToString(CR), "[CR_0D]", -1)
   374  	data = strings.Replace(data, AsciiToString(SO), "[SO_0E]", -1)
   375  	data = strings.Replace(data, AsciiToString(SI), "[SI_0F]", -1)
   376  	data = strings.Replace(data, AsciiToString(DLE), "[DLE_10]", -1)
   377  	data = strings.Replace(data, AsciiToString(DC1), "[DC1_11]", -1)
   378  	data = strings.Replace(data, AsciiToString(DC2), "[DC2_12]", -1)
   379  	data = strings.Replace(data, AsciiToString(DC3), "[DC3_13]", -1)
   380  	data = strings.Replace(data, AsciiToString(DC4), "[DC4_14]", -1)
   381  	data = strings.Replace(data, AsciiToString(NAK), "[NAK_15]", -1)
   382  	data = strings.Replace(data, AsciiToString(SYN), "[SYN_16]", -1)
   383  	data = strings.Replace(data, AsciiToString(ETB), "[ETB_17]", -1)
   384  	data = strings.Replace(data, AsciiToString(CAN), "[CAN_18]", -1)
   385  	data = strings.Replace(data, AsciiToString(EM), "[EM_19]", -1)
   386  	data = strings.Replace(data, AsciiToString(SUB), "[SUB_1A]", -1)
   387  	data = strings.Replace(data, AsciiToString(ESC), "[ESC_1B]", -1)
   388  	data = strings.Replace(data, AsciiToString(CR), "[CR_1C]", -1)
   389  	data = strings.Replace(data, AsciiToString(FS), "[FS_1D]", -1)
   390  	data = strings.Replace(data, AsciiToString(RS), "[RS_1E]", -1)
   391  	data = strings.Replace(data, AsciiToString(US), "[US_1F]", -1)
   392  
   393  	return data
   394  }
   395  
   396  // UnescapeNonPrintable converts pseudo escaped back to non printable form
   397  func UnescapeNonPrintable(data string) string {
   398  	data = strings.Replace(data, "[NUL_00]", AsciiToString(NUL), -1)
   399  	data = strings.Replace(data, "[SOH_01]", AsciiToString(SOH), -1)
   400  	data = strings.Replace(data, "[STX_02]", AsciiToString(STX), -1)
   401  	data = strings.Replace(data, "[ETX_03]", AsciiToString(ETX), -1)
   402  	data = strings.Replace(data, "[EOT_04]", AsciiToString(EOT), -1)
   403  	data = strings.Replace(data, "[ENQ_05]", AsciiToString(ENQ), -1)
   404  	data = strings.Replace(data, "[ACK_06]", AsciiToString(ACK), -1)
   405  	data = strings.Replace(data, "[BEL_07]", AsciiToString(BEL), -1)
   406  	data = strings.Replace(data, "[BS_08]", AsciiToString(BS), -1)
   407  	data = strings.Replace(data, "[HT_09]", AsciiToString(HT), -1)
   408  	data = strings.Replace(data, "[LF_0A]", AsciiToString(LF), -1)
   409  	data = strings.Replace(data, "[VT_0B]", AsciiToString(VT), -1)
   410  	data = strings.Replace(data, "[FF_0C]", AsciiToString(FF), -1)
   411  	data = strings.Replace(data, "[CR_0D]", AsciiToString(CR), -1)
   412  	data = strings.Replace(data, "[SO_0E]", AsciiToString(SO), -1)
   413  	data = strings.Replace(data, "[SI_0F]", AsciiToString(SI), -1)
   414  	data = strings.Replace(data, "[DLE_10]", AsciiToString(DLE), -1)
   415  	data = strings.Replace(data, "[DC1_11]", AsciiToString(DC1), -1)
   416  	data = strings.Replace(data, "[DC2_12]", AsciiToString(DC2), -1)
   417  	data = strings.Replace(data, "[DC3_13]", AsciiToString(DC3), -1)
   418  	data = strings.Replace(data, "[DC4_14]", AsciiToString(DC4), -1)
   419  	data = strings.Replace(data, "[NAK_15]", AsciiToString(NAK), -1)
   420  	data = strings.Replace(data, "[SYN_16]", AsciiToString(SYN), -1)
   421  	data = strings.Replace(data, "[ETB_17]", AsciiToString(ETB), -1)
   422  	data = strings.Replace(data, "[CAN_18]", AsciiToString(CAN), -1)
   423  	data = strings.Replace(data, "[EM_19]", AsciiToString(EM), -1)
   424  	data = strings.Replace(data, "[SUB_1A]", AsciiToString(SUB), -1)
   425  	data = strings.Replace(data, "[ESC_1B]", AsciiToString(ESC), -1)
   426  	data = strings.Replace(data, "[CR_1C]", AsciiToString(CR), -1)
   427  	data = strings.Replace(data, "[FS_1D]", AsciiToString(FS), -1)
   428  	data = strings.Replace(data, "[RS_1E]", AsciiToString(RS), -1)
   429  	data = strings.Replace(data, "[US_1F]", AsciiToString(US), -1)
   430  
   431  	return data
   432  }