001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.thrift2;
019
020import static java.nio.ByteBuffer.wrap;
021import static org.junit.Assert.assertArrayEquals;
022import static org.junit.Assert.assertEquals;
023import static org.junit.Assert.assertNull;
024import static org.junit.Assert.fail;
025
026import java.io.IOException;
027import java.nio.ByteBuffer;
028import java.security.PrivilegedExceptionAction;
029import java.util.ArrayList;
030import java.util.Arrays;
031import java.util.Collections;
032import java.util.Comparator;
033import java.util.List;
034import org.apache.hadoop.conf.Configuration;
035import org.apache.hadoop.hbase.HBaseClassTestRule;
036import org.apache.hadoop.hbase.HBaseTestingUtil;
037import org.apache.hadoop.hbase.TableName;
038import org.apache.hadoop.hbase.client.Admin;
039import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
040import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
041import org.apache.hadoop.hbase.client.Connection;
042import org.apache.hadoop.hbase.client.ConnectionFactory;
043import org.apache.hadoop.hbase.client.TableDescriptor;
044import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
045import org.apache.hadoop.hbase.security.User;
046import org.apache.hadoop.hbase.security.UserProvider;
047import org.apache.hadoop.hbase.security.visibility.ScanLabelGenerator;
048import org.apache.hadoop.hbase.security.visibility.SimpleScanLabelGenerator;
049import org.apache.hadoop.hbase.security.visibility.VisibilityClient;
050import org.apache.hadoop.hbase.security.visibility.VisibilityConstants;
051import org.apache.hadoop.hbase.security.visibility.VisibilityTestUtil;
052import org.apache.hadoop.hbase.security.visibility.VisibilityUtils;
053import org.apache.hadoop.hbase.testclassification.ClientTests;
054import org.apache.hadoop.hbase.testclassification.MediumTests;
055import org.apache.hadoop.hbase.thrift2.generated.TAppend;
056import org.apache.hadoop.hbase.thrift2.generated.TAuthorization;
057import org.apache.hadoop.hbase.thrift2.generated.TCellVisibility;
058import org.apache.hadoop.hbase.thrift2.generated.TColumn;
059import org.apache.hadoop.hbase.thrift2.generated.TColumnIncrement;
060import org.apache.hadoop.hbase.thrift2.generated.TColumnValue;
061import org.apache.hadoop.hbase.thrift2.generated.TDelete;
062import org.apache.hadoop.hbase.thrift2.generated.TGet;
063import org.apache.hadoop.hbase.thrift2.generated.TIllegalArgument;
064import org.apache.hadoop.hbase.thrift2.generated.TIncrement;
065import org.apache.hadoop.hbase.thrift2.generated.TPut;
066import org.apache.hadoop.hbase.thrift2.generated.TResult;
067import org.apache.hadoop.hbase.thrift2.generated.TScan;
068import org.apache.hadoop.hbase.util.Bytes;
069import org.junit.AfterClass;
070import org.junit.Assert;
071import org.junit.Before;
072import org.junit.BeforeClass;
073import org.junit.ClassRule;
074import org.junit.Test;
075import org.junit.experimental.categories.Category;
076import org.slf4j.Logger;
077import org.slf4j.LoggerFactory;
078
079import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsResponse;
080
081@Category({ ClientTests.class, MediumTests.class })
082public class TestThriftHBaseServiceHandlerWithLabels {
083
084  @ClassRule
085  public static final HBaseClassTestRule CLASS_RULE =
086    HBaseClassTestRule.forClass(TestThriftHBaseServiceHandlerWithLabels.class);
087
088  private static final Logger LOG =
089    LoggerFactory.getLogger(TestThriftHBaseServiceHandlerWithLabels.class);
090  private static final HBaseTestingUtil UTIL = new HBaseTestingUtil();
091
092  // Static names for tables, columns, rows, and values
093  private static byte[] tableAname = Bytes.toBytes("tableA");
094  private static byte[] familyAname = Bytes.toBytes("familyA");
095  private static byte[] familyBname = Bytes.toBytes("familyB");
096  private static byte[] qualifierAname = Bytes.toBytes("qualifierA");
097  private static byte[] qualifierBname = Bytes.toBytes("qualifierB");
098  private static byte[] valueAname = Bytes.toBytes("valueA");
099  private static byte[] valueBname = Bytes.toBytes("valueB");
100  private static ColumnFamilyDescriptor[] families = new ColumnFamilyDescriptor[] {
101    ColumnFamilyDescriptorBuilder.newBuilder(familyAname).setMaxVersions(3).build(),
102    ColumnFamilyDescriptorBuilder.newBuilder(familyBname).setMaxVersions(2).build() };
103
104  private final static String TOPSECRET = "topsecret";
105  private final static String PUBLIC = "public";
106  private final static String PRIVATE = "private";
107  private final static String CONFIDENTIAL = "confidential";
108  private final static String SECRET = "secret";
109  private static User SUPERUSER;
110
111  private static Configuration conf;
112
113  public void assertTColumnValuesEqual(List<TColumnValue> columnValuesA,
114    List<TColumnValue> columnValuesB) {
115    assertEquals(columnValuesA.size(), columnValuesB.size());
116    Comparator<TColumnValue> comparator = new Comparator<TColumnValue>() {
117      @Override
118      public int compare(TColumnValue o1, TColumnValue o2) {
119        return Bytes.compareTo(Bytes.add(o1.getFamily(), o1.getQualifier()),
120          Bytes.add(o2.getFamily(), o2.getQualifier()));
121      }
122    };
123    Collections.sort(columnValuesA, comparator);
124    Collections.sort(columnValuesB, comparator);
125
126    for (int i = 0; i < columnValuesA.size(); i++) {
127      TColumnValue a = columnValuesA.get(i);
128      TColumnValue b = columnValuesB.get(i);
129      assertArrayEquals(a.getFamily(), b.getFamily());
130      assertArrayEquals(a.getQualifier(), b.getQualifier());
131      assertArrayEquals(a.getValue(), b.getValue());
132    }
133  }
134
135  @BeforeClass
136  public static void beforeClass() throws Exception {
137    SUPERUSER = User.createUserForTesting(conf, "admin", new String[] { "supergroup" });
138    conf = UTIL.getConfiguration();
139    conf.setClass(VisibilityUtils.VISIBILITY_LABEL_GENERATOR_CLASS, SimpleScanLabelGenerator.class,
140      ScanLabelGenerator.class);
141    conf.set("hbase.superuser", SUPERUSER.getShortName());
142    VisibilityTestUtil.enableVisiblityLabels(conf);
143    UTIL.startMiniCluster(1);
144    // Wait for the labels table to become available
145    UTIL.waitTableEnabled(VisibilityConstants.LABELS_TABLE_NAME.getName(), 50000);
146    createLabels();
147    Admin admin = UTIL.getAdmin();
148    TableDescriptor tableDescriptor = TableDescriptorBuilder
149      .newBuilder(TableName.valueOf(tableAname)).setColumnFamilies(Arrays.asList(families)).build();
150    admin.createTable(tableDescriptor);
151    admin.close();
152    setAuths();
153  }
154
155  private static void createLabels() throws IOException, InterruptedException {
156    PrivilegedExceptionAction<VisibilityLabelsResponse> action =
157      new PrivilegedExceptionAction<VisibilityLabelsResponse>() {
158        @Override
159        public VisibilityLabelsResponse run() throws Exception {
160          String[] labels = { SECRET, CONFIDENTIAL, PRIVATE, PUBLIC, TOPSECRET };
161          try (Connection conn = ConnectionFactory.createConnection(conf)) {
162            VisibilityClient.addLabels(conn, labels);
163          } catch (Throwable t) {
164            throw new IOException(t);
165          }
166          return null;
167        }
168      };
169    SUPERUSER.runAs(action);
170  }
171
172  private static void setAuths() throws IOException {
173    String[] labels = { SECRET, CONFIDENTIAL, PRIVATE, PUBLIC, TOPSECRET };
174    try {
175      VisibilityClient.setAuths(UTIL.getConnection(), labels, User.getCurrent().getShortName());
176    } catch (Throwable t) {
177      throw new IOException(t);
178    }
179  }
180
181  @AfterClass
182  public static void afterClass() throws Exception {
183    UTIL.shutdownMiniCluster();
184  }
185
186  @Before
187  public void setup() throws Exception {
188
189  }
190
191  private ThriftHBaseServiceHandler createHandler() throws IOException {
192    return new ThriftHBaseServiceHandler(conf, UserProvider.instantiate(conf));
193  }
194
195  @Test
196  public void testScanWithVisibilityLabels() throws Exception {
197    ThriftHBaseServiceHandler handler = createHandler();
198    ByteBuffer table = wrap(tableAname);
199
200    // insert data
201    TColumnValue columnValue =
202      new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(valueAname));
203    List<TColumnValue> columnValues = new ArrayList<>(1);
204    columnValues.add(columnValue);
205    for (int i = 0; i < 10; i++) {
206      TPut put = new TPut(wrap(Bytes.toBytes("testScan" + i)), columnValues);
207      if (i == 5) {
208        put.setCellVisibility(new TCellVisibility().setExpression(PUBLIC));
209      } else {
210        put.setCellVisibility(new TCellVisibility()
211          .setExpression("(" + SECRET + "|" + CONFIDENTIAL + ")" + "&" + "!" + TOPSECRET));
212      }
213      handler.put(table, put);
214    }
215
216    // create scan instance
217    TScan scan = new TScan();
218    List<TColumn> columns = new ArrayList<>(1);
219    TColumn column = new TColumn();
220    column.setFamily(familyAname);
221    column.setQualifier(qualifierAname);
222    columns.add(column);
223    scan.setColumns(columns);
224    scan.setStartRow(Bytes.toBytes("testScan"));
225    scan.setStopRow(Bytes.toBytes("testScan\uffff"));
226
227    TAuthorization tauth = new TAuthorization();
228    List<String> labels = new ArrayList<>(2);
229    labels.add(SECRET);
230    labels.add(PRIVATE);
231    tauth.setLabels(labels);
232    scan.setAuthorizations(tauth);
233    // get scanner and rows
234    int scanId = handler.openScanner(table, scan);
235    List<TResult> results = handler.getScannerRows(scanId, 10);
236    assertEquals(9, results.size());
237    Assert.assertFalse(Bytes.equals(results.get(5).getRow(), Bytes.toBytes("testScan" + 5)));
238    for (int i = 0; i < 9; i++) {
239      if (i < 5) {
240        assertArrayEquals(Bytes.toBytes("testScan" + i), results.get(i).getRow());
241      } else if (i == 5) {
242        continue;
243      } else {
244        assertArrayEquals(Bytes.toBytes("testScan" + (i + 1)), results.get(i).getRow());
245      }
246    }
247
248    // check that we are at the end of the scan
249    results = handler.getScannerRows(scanId, 9);
250    assertEquals(0, results.size());
251
252    // close scanner and check that it was indeed closed
253    handler.closeScanner(scanId);
254    try {
255      handler.getScannerRows(scanId, 9);
256      fail("Scanner id should be invalid");
257    } catch (TIllegalArgument e) {
258    }
259  }
260
261  @Test
262  public void testGetScannerResultsWithAuthorizations() throws Exception {
263    ThriftHBaseServiceHandler handler = createHandler();
264    ByteBuffer table = wrap(tableAname);
265
266    // insert data
267    TColumnValue columnValue =
268      new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(valueAname));
269    List<TColumnValue> columnValues = new ArrayList<>(1);
270    columnValues.add(columnValue);
271    for (int i = 0; i < 20; i++) {
272      TPut put =
273        new TPut(wrap(Bytes.toBytes("testGetScannerResults" + pad(i, (byte) 2))), columnValues);
274      if (i == 3) {
275        put.setCellVisibility(new TCellVisibility().setExpression(PUBLIC));
276      } else {
277        put.setCellVisibility(new TCellVisibility()
278          .setExpression("(" + SECRET + "|" + CONFIDENTIAL + ")" + "&" + "!" + TOPSECRET));
279      }
280      handler.put(table, put);
281    }
282
283    // create scan instance
284    TScan scan = new TScan();
285    List<TColumn> columns = new ArrayList<>(1);
286    TColumn column = new TColumn();
287    column.setFamily(familyAname);
288    column.setQualifier(qualifierAname);
289    columns.add(column);
290    scan.setColumns(columns);
291    scan.setStartRow(Bytes.toBytes("testGetScannerResults"));
292
293    // get 5 rows and check the returned results
294    scan.setStopRow(Bytes.toBytes("testGetScannerResults05"));
295    TAuthorization tauth = new TAuthorization();
296    List<String> labels = new ArrayList<>(2);
297    labels.add(SECRET);
298    labels.add(PRIVATE);
299    tauth.setLabels(labels);
300    scan.setAuthorizations(tauth);
301    List<TResult> results = handler.getScannerResults(table, scan, 5);
302    assertEquals(4, results.size());
303    for (int i = 0; i < 4; i++) {
304      if (i < 3) {
305        assertArrayEquals(Bytes.toBytes("testGetScannerResults" + pad(i, (byte) 2)),
306          results.get(i).getRow());
307      } else if (i == 3) {
308        continue;
309      } else {
310        assertArrayEquals(Bytes.toBytes("testGetScannerResults" + pad(i + 1, (byte) 2)),
311          results.get(i).getRow());
312      }
313    }
314  }
315
316  @Test
317  public void testGetsWithLabels() throws Exception {
318    ThriftHBaseServiceHandler handler = createHandler();
319    byte[] rowName = Bytes.toBytes("testPutGet");
320    ByteBuffer table = wrap(tableAname);
321
322    List<TColumnValue> columnValues = new ArrayList<>(2);
323    columnValues.add(new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(valueAname)));
324    columnValues.add(new TColumnValue(wrap(familyBname), wrap(qualifierBname), wrap(valueBname)));
325    TPut put = new TPut(wrap(rowName), columnValues);
326
327    put.setColumnValues(columnValues);
328    put.setCellVisibility(new TCellVisibility()
329      .setExpression("(" + SECRET + "|" + CONFIDENTIAL + ")" + "&" + "!" + TOPSECRET));
330    handler.put(table, put);
331    TGet get = new TGet(wrap(rowName));
332    TAuthorization tauth = new TAuthorization();
333    List<String> labels = new ArrayList<>(2);
334    labels.add(SECRET);
335    labels.add(PRIVATE);
336    tauth.setLabels(labels);
337    get.setAuthorizations(tauth);
338    TResult result = handler.get(table, get);
339    assertArrayEquals(rowName, result.getRow());
340    List<TColumnValue> returnedColumnValues = result.getColumnValues();
341    assertTColumnValuesEqual(columnValues, returnedColumnValues);
342  }
343
344  @Test
345  public void testIncrementWithTags() throws Exception {
346    ThriftHBaseServiceHandler handler = createHandler();
347    byte[] rowName = Bytes.toBytes("testIncrementWithTags");
348    ByteBuffer table = wrap(tableAname);
349
350    List<TColumnValue> columnValues = new ArrayList<>(1);
351    columnValues
352      .add(new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(Bytes.toBytes(1L))));
353    TPut put = new TPut(wrap(rowName), columnValues);
354    put.setColumnValues(columnValues);
355    put.setCellVisibility(new TCellVisibility().setExpression(PRIVATE));
356    handler.put(table, put);
357
358    List<TColumnIncrement> incrementColumns = new ArrayList<>(1);
359    incrementColumns.add(new TColumnIncrement(wrap(familyAname), wrap(qualifierAname)));
360    TIncrement increment = new TIncrement(wrap(rowName), incrementColumns);
361    increment.setCellVisibility(new TCellVisibility().setExpression(SECRET));
362    handler.increment(table, increment);
363
364    TGet get = new TGet(wrap(rowName));
365    TAuthorization tauth = new TAuthorization();
366    List<String> labels = new ArrayList<>(1);
367    labels.add(SECRET);
368    tauth.setLabels(labels);
369    get.setAuthorizations(tauth);
370    TResult result = handler.get(table, get);
371
372    assertArrayEquals(rowName, result.getRow());
373    assertEquals(1, result.getColumnValuesSize());
374    TColumnValue columnValue = result.getColumnValues().get(0);
375    assertArrayEquals(Bytes.toBytes(2L), columnValue.getValue());
376  }
377
378  @Test
379  public void testIncrementWithTagsWithNotMatchLabels() throws Exception {
380    ThriftHBaseServiceHandler handler = createHandler();
381    byte[] rowName = Bytes.toBytes("testIncrementWithTagsWithNotMatchLabels");
382    ByteBuffer table = wrap(tableAname);
383
384    List<TColumnValue> columnValues = new ArrayList<>(1);
385    columnValues
386      .add(new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(Bytes.toBytes(1L))));
387    TPut put = new TPut(wrap(rowName), columnValues);
388    put.setColumnValues(columnValues);
389    put.setCellVisibility(new TCellVisibility().setExpression(PRIVATE));
390    handler.put(table, put);
391
392    List<TColumnIncrement> incrementColumns = new ArrayList<>(1);
393    incrementColumns.add(new TColumnIncrement(wrap(familyAname), wrap(qualifierAname)));
394    TIncrement increment = new TIncrement(wrap(rowName), incrementColumns);
395    increment.setCellVisibility(new TCellVisibility().setExpression(SECRET));
396    handler.increment(table, increment);
397
398    TGet get = new TGet(wrap(rowName));
399    TAuthorization tauth = new TAuthorization();
400    List<String> labels = new ArrayList<>(1);
401    labels.add(PUBLIC);
402    tauth.setLabels(labels);
403    get.setAuthorizations(tauth);
404    TResult result = handler.get(table, get);
405    assertNull(result.getRow());
406  }
407
408  @Test
409  public void testAppend() throws Exception {
410    ThriftHBaseServiceHandler handler = createHandler();
411    byte[] rowName = Bytes.toBytes("testAppend");
412    ByteBuffer table = wrap(tableAname);
413    byte[] v1 = Bytes.toBytes(1L);
414    byte[] v2 = Bytes.toBytes(5L);
415    List<TColumnValue> columnValues = new ArrayList<>(1);
416    columnValues
417      .add(new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(Bytes.toBytes(1L))));
418    TPut put = new TPut(wrap(rowName), columnValues);
419    put.setColumnValues(columnValues);
420    put.setCellVisibility(new TCellVisibility().setExpression(PRIVATE));
421    handler.put(table, put);
422
423    List<TColumnValue> appendColumns = new ArrayList<>(1);
424    appendColumns.add(new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(v2)));
425    TAppend append = new TAppend(wrap(rowName), appendColumns);
426    append.setCellVisibility(new TCellVisibility().setExpression(SECRET));
427    handler.append(table, append);
428
429    TGet get = new TGet(wrap(rowName));
430    TAuthorization tauth = new TAuthorization();
431    List<String> labels = new ArrayList<>(1);
432    labels.add(SECRET);
433    tauth.setLabels(labels);
434    get.setAuthorizations(tauth);
435    TResult result = handler.get(table, get);
436
437    assertArrayEquals(rowName, result.getRow());
438    assertEquals(1, result.getColumnValuesSize());
439    TColumnValue columnValue = result.getColumnValues().get(0);
440    assertArrayEquals(Bytes.add(v1, v2), columnValue.getValue());
441  }
442
443  @Test
444  public void testDeleteWithLabels() throws Exception {
445    ThriftHBaseServiceHandler handler = createHandler();
446    byte[] rowName = "testPutGetDeleteGet".getBytes();
447    ByteBuffer table = wrap(tableAname);
448
449    // common auths
450    TAuthorization tauth = new TAuthorization();
451    List<String> labels = new ArrayList<String>();
452    labels.add(SECRET);
453    labels.add(PRIVATE);
454    tauth.setLabels(labels);
455
456    // put
457    List<TColumnValue> columnValues = new ArrayList<TColumnValue>();
458    columnValues.add(new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(valueAname)));
459    columnValues.add(new TColumnValue(wrap(familyBname), wrap(qualifierBname), wrap(valueBname)));
460    TPut put = new TPut(wrap(rowName), columnValues);
461
462    put.setColumnValues(columnValues);
463    put.setCellVisibility(new TCellVisibility()
464      .setExpression("(" + SECRET + "|" + CONFIDENTIAL + ")" + "&" + "!" + TOPSECRET));
465    handler.put(table, put);
466
467    // verify put
468    TGet get = new TGet(wrap(rowName));
469    get.setAuthorizations(tauth);
470    TResult result = handler.get(table, get);
471    assertArrayEquals(rowName, result.getRow());
472    List<TColumnValue> returnedColumnValues = result.getColumnValues();
473    assertTColumnValuesEqual(columnValues, returnedColumnValues);
474
475    // delete
476    TDelete delete = new TDelete(wrap(rowName));
477    delete.setCellVisibility(new TCellVisibility()
478      .setExpression("(" + SECRET + "|" + CONFIDENTIAL + ")" + "&" + "!" + TOPSECRET));
479    handler.deleteSingle(table, delete);
480
481    // verify delete
482    TGet get2 = new TGet(wrap(rowName));
483    get2.setAuthorizations(tauth);
484    TResult result2 = handler.get(table, get2);
485    assertNull(result2.getRow());
486  }
487
488  @Test
489  public void testDeleteWithLabelsNegativeTest() throws Exception {
490    ThriftHBaseServiceHandler handler = createHandler();
491    byte[] rowName = "testPutGetTryDeleteGet".getBytes();
492    ByteBuffer table = wrap(tableAname);
493
494    // common auths
495    TAuthorization tauth = new TAuthorization();
496    List<String> labels = new ArrayList<String>();
497    labels.add(SECRET);
498    labels.add(PRIVATE);
499    tauth.setLabels(labels);
500
501    // put
502    List<TColumnValue> columnValues = new ArrayList<TColumnValue>();
503    columnValues.add(new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(valueAname)));
504    columnValues.add(new TColumnValue(wrap(familyBname), wrap(qualifierBname), wrap(valueBname)));
505    TPut put = new TPut(wrap(rowName), columnValues);
506
507    put.setColumnValues(columnValues);
508    put.setCellVisibility(new TCellVisibility()
509      .setExpression("(" + SECRET + "|" + CONFIDENTIAL + ")" + "&" + "!" + TOPSECRET));
510    handler.put(table, put);
511
512    // verify put
513    TGet get = new TGet(wrap(rowName));
514    get.setAuthorizations(tauth);
515    TResult result = handler.get(table, get);
516    assertArrayEquals(rowName, result.getRow());
517    List<TColumnValue> returnedColumnValues = result.getColumnValues();
518    assertTColumnValuesEqual(columnValues, returnedColumnValues);
519
520    // _try_ delete with _no_ CellVisibility
521    TDelete delete = new TDelete(wrap(rowName));
522    handler.deleteSingle(table, delete);
523
524    // verify delete did in fact _not_ work
525    TGet get2 = new TGet(wrap(rowName));
526    get2.setAuthorizations(tauth);
527    TResult result2 = handler.get(table, get2);
528    assertArrayEquals(rowName, result2.getRow());
529  }
530
531  /**
532   * Padding numbers to make comparison of sort order easier in a for loop The number to pad.
533   * @param n   The number to pad.
534   * @param pad The length to pad up to.
535   * @return The padded number as a string.
536   */
537  private String pad(int n, byte pad) {
538    String res = Integer.toString(n);
539
540    while (res.length() < pad) {
541      res = "0" + res;
542    }
543
544    return res;
545  }
546}