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