Skip to content

Instantly share code, notes, and snippets.

@rponte
Last active July 13, 2025 17:48
Show Gist options
  • Select an option

  • Save rponte/06f228cc370a4cbba6b23aae88301711 to your computer and use it in GitHub Desktop.

Select an option

Save rponte/06f228cc370a4cbba6b23aae88301711 to your computer and use it in GitHub Desktop.
Example of a simple TLV parser (decoder)
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Class responsible for parsing an encoded-text in TLV (Tag-Length-Value) format
*/
public class TLVParser {
private static final Logger logger = LogManager.getLogger(TLVParser.class);
/**
* The minimum TLV size allowed
*/
private static final int MIN_TLV_SIZE = 7;
/**
* Holds the original TLV encoded-text
*/
private final String tlv;
/**
* Holds all tags keeping their insertion order
*/
private final Map<String, String> tlvData = new LinkedHashMap<>();
/**
* Creates a parser for the specified <code>tlv</code>
*
* @param tlv the TLV encoded-text that will be parsed
*/
public TLVParser(String tlv) {
this.tlv = tlv;
parses(tlv);
}
/**
* Parses TLV encoded-text into a Map
*/
private void parses(String tlv) {
if (tlv == null) {
logger.error("Invalid TLV data: can not be null");
return;
}
if (tlv.length() < MIN_TLV_SIZE) {
logger.error("Invalid TLV data: must be at least 7 characters long (tlv='" + tlv + "')");
return;
}
try {
int i = 0;
while (true) {
String tag = tlv.substring(i, i+3);
int len = Integer.parseInt(tlv.substring(i+3, i+6));
String value = tlv.substring(i+6, i+6+len);
tlvData.put(tag, value);
i = i+6+len;
if (i >= tlv.length())
break;
}
} catch (Exception e) {
logger.error("Unexpected error while parsing TLV data: tlv='" + tlv + "'");
}
}
/**
* Finds the tag value by the specified <code>tagName</code>
*
* @param tagName the tag name
* @return the tag value or <code>null</code> if not found
*/
public String getTag(String tagName) {
return tlvData.get(tagName);
}
/**
* Finds the tag value by the specified <code>tagName</code>. In case the tag is
* not found, returns the <code>defaultValue</code>
*
* @param tagName the tag name
* @param defaultValue the default value that will used in case a tag is not
* found
* @return the tag value or <code>defaultValue</code> if not found
*/
public String getTag(String tagName, String defaultValue) {
String tagValue = getTag(tagName);
if (tagValue == null) {
return defaultValue;
}
return tagValue;
}
/**
* Returns the total of tags found
*/
public int getTotalOfTags() {
return tlvData.size();
}
@Override
public String toString() {
return "TLVParser [tlv=" + tlv
+ ", totalOfTags=" + getTotalOfTags()
+ ", tlvData=" + tlvData
+ "]";
}
}
import static org.apache.commons.lang3.StringUtils.repeat;
import static org.junit.Assert.assertEquals;
import org.apache.commons.lang3.StringUtils;
import org.junit.Test;
public class TLVParserTest {
@Test
public void shouldParserTLVEncodedText() {
String tlv = join("txt006rponte",
"IN10042020",
"in2009000000666",
"lo100820019393",
"bi201100000014299",
"dt201007/03/1984",
"dt300820:20:01",
"501002GP",
"wsc006 aa "
);
TLVParser parser = new TLVParser(tlv);
assertEquals("total of tags", 9, parser.getTotalOfTags());
assertEquals("tag: txt", "rponte", parser.getTag("txt"));
assertEquals("tag: IN1", "2020", parser.getTag("IN1"));
assertEquals("tag: in2", "000000666", parser.getTag("in2"));
assertEquals("tag: lo1", "20019393", parser.getTag("lo1"));
assertEquals("tag: bi2", "00000014299", parser.getTag("bi2"));
assertEquals("tag: dt2", "07/03/1984", parser.getTag("dt2"));
assertEquals("tag: dt3", "20:20:01", parser.getTag("dt3"));
assertEquals("tag: 501", "GP", parser.getTag("501"));
assertEquals("tag: wsc", " aa ", parser.getTag("wsc"));
}
@Test
public void shouldParserTLVEncodedText_asMuchAsPossible() {
String tlvWithInvalidTag = join("txt006rponte",
"IN10042020",
"in2009000000666",
"lo100820019393",
"inv0xx90", // invalid
"aaa001a",
"bbb001b"
);
TLVParser parser = new TLVParser(tlvWithInvalidTag);
assertEquals("total of tags", 4, parser.getTotalOfTags());
assertEquals("tag: txt", "rponte", parser.getTag("txt"));
assertEquals("tag: IN1", "2020", parser.getTag("IN1"));
assertEquals("tag: in2", "000000666", parser.getTag("in2"));
assertEquals("tag: lo1", "20019393", parser.getTag("lo1"));
assertEquals("tag: inv", null, parser.getTag("inv"));
assertEquals("tag: aaa", null, parser.getTag("aaa"));
assertEquals("tag: bbb", null, parser.getTag("bbb"));
}
@Test
public void shouldParserTLVEncodedText_andReturnDefaultValueIfTagNotFound() {
String tlv = join("aaa001a",
"bbb001b"
);
TLVParser parser = new TLVParser(tlv);
assertEquals("total of tags", 2, parser.getTotalOfTags());
assertEquals("tag: aaa", "a", parser.getTag("aaa", "x"));
assertEquals("tag: bbb", "b", parser.getTag("bbb", "y"));
assertEquals("tag: ccc", "z", parser.getTag("ccc", "z"));
}
@Test
public void shouldNotParserTLVEncodedText_whenTextHasLessThan7Characters() {
String tlv = "abcdef";
TLVParser parser = new TLVParser(tlv);
assertEquals("total of tags", 0, parser.getTotalOfTags());
}
@Test
public void shouldNotParserTLVEncodedText_whenTextIsNull() {
String tlv = null;
TLVParser parser = new TLVParser(tlv);
assertEquals("total of tags", 0, parser.getTotalOfTags());
}
@Test
public void shouldNotParserTLVEncodedText_whenTextIsEmpty() {
String tlv = "";
TLVParser parser = new TLVParser(tlv);
assertEquals("total of tags", 0, parser.getTotalOfTags());
}
@Test
public void shouldNotParserTLVEncodedText_whenTextIsBlank() {
String tlv = repeat(" ", 12);
TLVParser parser = new TLVParser(tlv);
assertEquals("total of tags", 0, parser.getTotalOfTags());
}
private String join(String...tlv) {
return StringUtils.join(tlv, "");
}
}
@rponte
Copy link
Author

rponte commented Jun 18, 2020

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment