iTextSharp 5.5.6 PdfCopy Failing with "Cannot access a closed file" - itext

This seems to be similar to this question: Merging Tagged PDF without ruining the tags
I'm using the latest iTextSharp NuGet package (v5.5.6) trying to merge two tagged PDFs. When calling Document.Close() I'm getting an ObjectDisposedException originating from PdfCopy.FlushIndirectObjects().
at System.IO.__Error.FileNotOpen()
at System.IO.FileStream.get_Position()
at iTextSharp.text.io.RAFRandomAccessSource.Get(Int64 position, Byte[] bytes, Int32 off, Int32 len) in d:\Downloads\itextsharp-master\src\core\iTextSharp\text\io\RAFRandomAccessSource.cs:line 96
at iTextSharp.text.io.IndependentRandomAccessSource.Get(Int64 position, Byte[] bytes, Int32 off, Int32 len) in d:\Downloads\itextsharp-master\src\core\iTextSharp\text\io\IndependentRandomAccessSource.cs:line 76
at iTextSharp.text.pdf.RandomAccessFileOrArray.Read(Byte[] b, Int32 off, Int32 len) in d:\Downloads\itextsharp-master\src\core\iTextSharp\text\pdf\RandomAccessFileOrArray.cs:line 235
at iTextSharp.text.pdf.RandomAccessFileOrArray.ReadFully(Byte[] b, Int32 off, Int32 len) in d:\Downloads\itextsharp-master\src\core\iTextSharp\text\pdf\RandomAccessFileOrArray.cs:line 264
at iTextSharp.text.pdf.RandomAccessFileOrArray.ReadFully(Byte[] b) in d:\Downloads\itextsharp-master\src\core\iTextSharp\text\pdf\RandomAccessFileOrArray.cs:line 254
at iTextSharp.text.pdf.PdfReader.GetStreamBytesRaw(PRStream stream, RandomAccessFileOrArray file) in d:\Downloads\itextsharp-master\src\core\iTextSharp\text\pdf\PdfReader.cs:line 2406
at iTextSharp.text.pdf.PdfReader.GetStreamBytesRaw(PRStream stream) in d:\Downloads\itextsharp-master\src\core\iTextSharp\text\pdf\PdfReader.cs:line 2443
at iTextSharp.text.pdf.PRStream.ToPdf(PdfWriter writer, Stream os) in d:\Downloads\itextsharp-master\src\core\iTextSharp\text\pdf\PRStream.cs:line 224
at iTextSharp.text.pdf.PdfIndirectObject.WriteTo(Stream os) in d:\Downloads\itextsharp-master\src\core\iTextSharp\text\pdf\PdfIndirectObject.cs:line 157
at iTextSharp.text.pdf.PdfWriter.PdfBody.Write(PdfIndirectObject indirect, Int32 refNumber, Int32 generation) in d:\Downloads\itextsharp-master\src\core\iTextSharp\text\pdf\PdfWriter.cs:line 389
at iTextSharp.text.pdf.PdfWriter.PdfBody.Add(PdfObject objecta, Int32 refNumber, Int32 generation, Boolean inObjStm) in d:\Downloads\itextsharp-master\src\core\iTextSharp\text\pdf\PdfWriter.cs:line 379
at iTextSharp.text.pdf.PdfCopy.WriteObjectToBody(PdfIndirectObject objecta) in d:\Downloads\itextsharp-master\src\core\iTextSharp\text\pdf\PdfCopy.cs:line 1238
at iTextSharp.text.pdf.PdfCopy.FlushIndirectObjects() in d:\Downloads\itextsharp-master\src\core\iTextSharp\text\pdf\PdfCopy.cs:line 1186
at iTextSharp.text.pdf.PdfCopy.FlushTaggedObjects() in d:\Downloads\itextsharp-master\src\core\iTextSharp\text\pdf\PdfCopy.cs:line 884
at iTextSharp.text.pdf.PdfDocument.Close() in d:\Downloads\itextsharp-master\src\core\iTextSharp\text\pdf\PdfDocument.cs:line 825
Here is the code that is producing the exception. If I don't call copy.SetTagged() and don't pass true as the third argument to GetImportedPage() the code executes without exception, but ignores all tagging.
using(var ms = new MemoryStream())
{
var doc = new Document();
var copy = new PdfSmartCopy(doc, ms);
copy.SetTagged();
doc.Open();
string[] files = new string[]{#"d:\tagged.pdf", #"d:\tagged.pdf"};
foreach(var f in files)
{
var reader = new PdfReader(f);
int pages = reader.NumberOfPages;
for(int i = 0; i < pages;)
copy.AddPage(copy.GetImportedPage(reader, ++i, true));
copy.FreeReader(reader);
reader.Close();
}
// ObjectDisposedException
doc.Close();
ms.Flush();
File.WriteAllBytes(#"d:\pdf.merged.v5.pdf", ms.ToArray());
}
Looking at the 5.5.6 source branch it looks like RAFRandomAccessSource.cs line 96 is the culprit.
public virtual int Get(long position, byte[] bytes, int off, int len) {
if (position > length)
return -1;
// Not thread safe!
if (raf.Position != position)
raf.Position has been disposed at this point, but I can't tell from where it has been disposed.
I'm hoping that I just need to do something more than simply call copy.SetTagged() and pass true to GetImportedPage() to fix the issue.

You are closing the PdfReader instances too early. You can only trigger:
reader.Close();
after you close the PdfSmartCopy instance, hence you have to rethink where you create the different PdfReader objects (not inside the loop).
The reason why the different PdfReader instances have to remain open is purely technical: merging structured trees (where all the tagging information is stored) isn't trivial. This can only happen at the moment all the other work is done. It requires access to the original structures of the separate documents. If you close the PdfReader to such a document, that structure can no longer be retrieved.

Related

Solution to upload image file via WCF service?

Being surfing for last 3-4 days downloading, running and fixing issues with available demo projects online, none of them work so far.
I need to upload an image using WCF webservice. Where from client side end I like to upload it by means of form (multipart/form-data), including some file description.
Any solution working with proper answer? My mind is really stacked overflow trying different solution. One which I initially have I am able to upload a text file where file gets created with some extra content in it. I need to upload image file.
------------cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5
Content-Disposition: form-data; name=\"Filename\"
testing file gets upload...
When I upload image file, the image file is empty.
Initial Code (one implantation), method by means of which I get the .txt file as above, in case of image its blank (or say corrupt don't know)
private string uplaodFile(Stream stream)
{
StreamReader sr = new StreamReader(stream);
int length = sr.ReadToEnd().Length;
byte[] buffer = new byte[length];
stream.Read(buffer, 0, length);
FileStream f = new FileStream(Path.Combine(HostingEnvironment.MapPath("~/Upload"), "test.png"), FileMode.OpenOrCreate);
f.Write(buffer, 0, buffer.Length);
f.Close();
stream.Close();
return "Recieved the image on server";
}
another;
public Stream FileUpload(string fileName, Stream stream)
{
string FilePath = Path.Combine(HostingEnvironment.MapPath("~/Upload"), fileName);
int length = 0;
using (FileStream writer = new FileStream(FilePath, FileMode.Create))
{
int readCount;
var buffer = new byte[8192];
while ((readCount = stream.Read(buffer, 0, buffer.Length)) != 0)
{
writer.Write(buffer, 0, readCount);
length += readCount;
}
}
return returnJson(new { resp_code = 302, resp_message = "occurred." });
}

iText7: Error at file pointer when merging two pdfs

We are in the last steps of evaluating iText7. We use iText 7.1.0 and html2pdf 2.0.0.
What we do: we send a json_encoded collection with pdf-data (which includes html for header, body and footer) to our Java app. There we iterate over the collection, create a byteArrayOutputStream for each pdf-data element and merge them together. We then send the results to a script which echoes it to e.g. a browser. Although the pdf is displayed correctly, we encounter errors while creating it:
com.itextpdf.io.IOException: Error at file pointer 226,416.
...
Caused by: com.itextpdf.io.IOException: xref subsection not found.
... 73 common frames omitted
If we create only one part of the collection, no error is thrown.
Iterate over collection and merge:
#RequestMapping(value = "/pdf", method = RequestMethod.POST, produces = MediaType.APPLICATION_PDF_VALUE)
public byte[] index(#RequestBody PDFDataModelCollection elements, Model model) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
PdfWriter writer = new PdfWriter(byteArrayOutputStream);
try (PdfDocument resultDoc = new PdfDocument(writer)) {
for (PDFDataModel pdfDataModel : elements.getElements()) {
PdfReader reader = new PdfReader(new ByteArrayInputStream(creationService.createDatasheet(pdfDataModel)));
try (PdfDocument sourceDoc = new PdfDocument(reader)) {
int n = sourceDoc.getNumberOfPages(); //<-- IOException on second iteration
for (int i = 1; i <= n; i++) {
PdfPage page = sourceDoc.getPage(i).copyTo(resultDoc);
resultDoc.addPage(page);
}
}
}
}
return byteArrayOutputStream.toByteArray(); //outputs the final pdf
}
Creation of part:
public byte[] createDatasheet(PDFDataModel pdfDataModel) throws IOException {
PdfWriter writer = new PdfWriter(byteArrayOutputStream);
//Initialize PDF document
PdfDocument pdfDoc = new PdfDocument(writer);
try (
Document document = new Document(pdfDoc)
) {
//header, footer, etc
//body
for (IElement element : HtmlConverter.convertToElements(pdfDataModel.getBody(), this.props)) {
document.add((IBlockElement) element);
}
footer.writeTotalNumberOnPages(pdfDoc);
}
return byteArrayOutputStream.toByteArray();
}
We are grateful for any suggestion.
In createDatasheet you appear to re-use some byteArrayOutputStream without clearing it first.
In the first iteration, therefore, everything works as desired, at the end of createDatasheet you have a single PDF file in it.
In the second iteration, though, you have two PDF files in that byteArrayOutputStream, one after the other. This concatenation does not form a valid single PDF.
Thus, byteArrayOutputStream.toByteArray() returns something broken.
To fix this, either make the byteArrayOutputStream local to createDatasheet and create a new instance every time or alternatively reset byteArrayOutputStream at the start of createDatasheet:
public byte[] createDatasheet(PDFDataModel pdfDataModel) throws IOException {
byteArrayOutputStream.reset();
PdfWriter writer = new PdfWriter(byteArrayOutputStream);
[...]

How to place the Same Digital signatures to Multiple places in PDF using itextsharp.net

I have implemented Digital Signature using iTextSharp Dll to sign PDF files with a single signature creating empty signature fields and update the signature field with signed hash working fine. Now, I want to place the same digital signature in every page of pdf. It's my client requirement.
I’m using the following code:
public class MyExternalSignatureContainer : IExternalSignatureContainer
{
private readonly byte[] signedBytes;
public MyExternalSignatureContainer(byte[] signedBytes)
{
this.signedBytes = signedBytes;
}
public byte[] Sign(Stream data)
{
return signedBytes;
}
public void ModifySigningDictionary(PdfDictionary signDic)
{
}
}
Below code used in program
PdfReader reader = new PdfReader(unsignedPdf);
FileStream os = File.OpenWrite(tempPdf);
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.Reason = "Reason1";
appearance.Contact = "";
appearance.Location = "Location1";
appearance.Acro6Layers = false;
appearance.Image = null;
appearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.DESCRIPTION;
appearance.SetVisibleSignature(new iTextSharp.text.Rectangle(36, 748, 144, 780), 1, null);
for (int i = 1; i < 8; i++)
{
var signatureField = PdfFormField.CreateSignature(stamper.Writer);
var signatureRect = new Rectangle(200, 200, 100, 100);
signatureField.Put(PdfName.T, new PdfString("ClientSignature_"+i.ToString()));
PdfIndirectReference PRef = stamper.Writer.PdfIndirectReference;
signatureField.Put(PdfName.V, PRef);
signatureField.Put(PdfName.F, new PdfNumber("132"));
signatureField.SetWidget(signatureRect, null);
signatureField.Put(PdfName.SUBTYPE, PdfName.WIDGET);
PdfDictionary xobject1 = new PdfDictionary();
PdfDictionary xobject2 = new PdfDictionary();
xobject1.Put(PdfName.N, appearance.GetAppearance().IndirectReference);
xobject2.Put(PdfName.AP, xobject1);
signatureField.Put(PdfName.AP, xobject1);
signatureField.SetPage();
PdfDictionary xobject3 = new PdfDictionary();
PdfDictionary xobject4 = new PdfDictionary();
xobject4.Put(PdfName.FRM, appearance.GetAppearance().IndirectReference);
xobject3.Put(PdfName.XOBJECT, xobject4);
signatureField.Put(PdfName.DR, xobject3);
stamper.AddAnnotation(signatureField, i);
}
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKMS, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(appearance, external, 8192);
stamper.Close();
byte[] SignedHash = DoEsign(SHA256Managed.Create().ComputeHash(appearance.GetRangeStream());
os.close();
reader.close();
reader = new PdfReader(tempPdf))
os = File.OpenWrite(signedPdf)
IExternalSignatureContainer external1 = new MyExternalSignatureContainer(SignedHash);
MakeSignature.SignDeferred(reader, signatureFieldName, os, external1);
os.close();
reader.close();
Please suggest me to complete the task
To give all signature fields the same single value wrapping the newly created signature container, they must all reference the same indirect object as value. Unfortunately iText creates the indirect object for the signature value only after the application code had the chance to add its additional fields which in turn require a reference to that signature value object. Thus, the application code has to anticipate the object number that indirect object will have.
This anticipation or prediction of the object number is very delicate, it depends on the exact same use case and can also become incorrect as the result of minor changes in the iTextSharp library
To make this easier, the application code should add those signature fields with their signature value references as late as possible, so there are as few other new indirect objects created as possible until iText creates the value indirect object.
As it turns out, the ModifySigningDictionary method of an IExternalSignatureContainer is a good position for that.
As soon as one adds one's code there, another issue pops up: There is no means to set the anticipated object number in a PdfIndirectReference instance externally. One way to get around this is to mimic such a reference using a PdfLiteral. (Well, probably one could also use reflection for this.)
Furthermore it turns out that one best creates the appearance streams to use by all one's additional signature fields before building that PdfLiteral mimicking a PdfIndirectReference as this simplifies the calculation of the object number iText will use for the actual value object.
With this in mind, here a proof-of concept. This proof of concept makes use of an IExternalSignature instance for actually signing. This is not a necessary precondition, one can also use an IExternalSignatureContainer instead with only a few changes, even an ExternalBlankSignatureContainer as in the question to later finalize the signature using MakeSignature.SignDeferred.
So given cipher parameters cp (private key material, e.g. pk.Key for an Org.BouncyCastle.Pkcs.AsymmetricKeyEntry pk) and a certificate chain chain, one would use
PdfReader reader = new PdfReader(SRC);
FileStream os = new FileStream(DEST, FileMode.Create, FileAccess.Write);
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.Reason = "Reason1";
appearance.Contact = "";
appearance.Location = "Location1";
appearance.Acro6Layers = false;
appearance.Image = null;
appearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.DESCRIPTION;
appearance.SetVisibleSignature(new iTextSharp.text.Rectangle(10, 10, 100, 100), reader.NumberOfPages, null);
IExternalSignature externalSignature = new PrivateKeySignature(cp, "SHA-256");
AllPagesSignatureContainer allPagesContainer = new AllPagesSignatureContainer(appearance, externalSignature, chain);
MakeSignature.SignExternalContainer(appearance, allPagesContainer, 8192);
with this external signature container class
public class AllPagesSignatureContainer : IExternalSignatureContainer
{
public AllPagesSignatureContainer(PdfSignatureAppearance appearance, IExternalSignature externalSignature, ICollection<X509Certificate> chain)
{
this.appearance = appearance;
this.chain = chain;
this.externalSignature = externalSignature;
}
public void ModifySigningDictionary(PdfDictionary signDic)
{
signDic.Put(PdfName.FILTER, PdfName.ADOBE_PPKMS);
signDic.Put(PdfName.SUBFILTER, PdfName.ADBE_PKCS7_DETACHED);
PdfStamper stamper = appearance.Stamper;
PdfReader reader = stamper.Reader;
PdfDictionary xobject1 = new PdfDictionary();
PdfDictionary xobject2 = new PdfDictionary();
xobject1.Put(PdfName.N, appearance.GetAppearance().IndirectReference);
xobject2.Put(PdfName.AP, xobject1);
PdfIndirectReference PRef = stamper.Writer.PdfIndirectReference;
PdfLiteral PRefLiteral = new PdfLiteral((PRef.Number + 1 + 2*(reader.NumberOfPages - 1)) + " 0 R");
for (int i = 1; i < reader.NumberOfPages; i++)
{
var signatureField = PdfFormField.CreateSignature(stamper.Writer);
signatureField.Put(PdfName.T, new PdfString("ClientSignature_" + i.ToString()));
signatureField.Put(PdfName.V, PRefLiteral);
signatureField.Put(PdfName.F, new PdfNumber("132"));
signatureField.SetWidget(appearance.Rect, null);
signatureField.Put(PdfName.SUBTYPE, PdfName.WIDGET);
signatureField.Put(PdfName.AP, xobject1);
signatureField.SetPage();
Console.WriteLine(signatureField);
stamper.AddAnnotation(signatureField, i);
}
}
public byte[] Sign(Stream data)
{
String hashAlgorithm = externalSignature.GetHashAlgorithm();
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, false);
IDigest messageDigest = DigestUtilities.GetDigest(hashAlgorithm);
byte[] hash = DigestAlgorithms.Digest(data, hashAlgorithm);
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
byte[] extSignature = externalSignature.Sign(sh);
sgn.SetExternalDigest(extSignature, null, externalSignature.GetEncryptionAlgorithm());
return sgn.GetEncodedPKCS7(hash, null, null, null, CryptoStandard.CMS);
}
PdfSignatureAppearance appearance;
ICollection<X509Certificate> chain;
IExternalSignature externalSignature;
}
The predicted indirect object number of the signature value in the line
PdfIndirectReference PRef = stamper.Writer.PdfIndirectReference;
PdfLiteral PRefLiteral = new PdfLiteral((PRef.Number + 1 + 2*(reader.NumberOfPages - 1)) + " 0 R");
strictly depends upon the use case being "exactly one signature field per page". For different use cases the estimate the prediction would differ.
I stress this here once again because e.g. the OP of this question did not take this into account when trying "to place multiple signatures on single page".
Another strict requirement for the object number prediction above is that the PdfStamper is created as above, i.e. not in append mode. If the signature is applied as an incremental update, i.e. in append mode, the lines above have to be replaced by
stamper.Writer.AddToBody(new PdfNull(), stamper.Writer.PdfIndirectReference, true);
PdfIndirectReference PRef = stamper.Writer.PdfIndirectReference;
PdfLiteral PRefLiteral = new PdfLiteral((PRef.Number + reader.NumberOfPages) + " 0 R");
This made a difference in the context of this question; the first line, adding an indirect null object to the PDF, is necessary to make sure that in case of PDFs with object streams the object stream object number has already been determined and does not slip between the next objects, resulting in an off-by-one error for our prediction.
Beware: While this procedure creates something which does not violate the letter of the PDF specifications (which only forbid the cases where the same field object is referenced from multiple pages, be it via the same or via distinct widgets), it clearly does violate its intent, its spirit. Thus, this procedure might also become forbidden as part of a Corrigenda document for the specification.

How can I get ByteRange with iText7?

As the picture show , I need to get byte array from ByteRange to do some verify , they are 0 to 840 and 960 to 1200.
I found the similar question : In Itext 7, how to get the range stream to sign a pdf?
iText in its own verification code needs to do the same thing. It does so in its SignatureUtil class. Thus, one can simply borrow from that code, e.g. like this:
try ( PdfReader pdfReader = new PdfReader(SOURCE_PDF);
PdfDocument pdfDocument = new PdfDocument(pdfReader);) {
SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);
for (String name : signatureUtil.getSignatureNames()) {
PdfSignature signature = signatureUtil.getSignature(name);
PdfArray b = signature.getByteRange();
RandomAccessFileOrArray rf = pdfReader.getSafeFile();
try ( InputStream rg = new RASInputStream(new RandomAccessSourceFactory().createRanged(rf.createSourceView(), SignatureUtil.asLongArray(b)));
OutputStream result = TARGET_STREAM_FOR_name_BYTES) {
byte[] buf = new byte[8192];
int rd;
while ((rd = rg.read(buf, 0, buf.length)) > 0) {
result.write(buf, 0, rd);
}
}
}
}
(RetrieveSignedRanges test testExtractSignedBytes)
If you want the byte range as a byte[] in memory, you can use a ByteArrayOutputStream as TARGET_STREAM_FOR_name_BYTES and retrieve the resulting byte array from it.

How stringbuilder append work

How many objects will be created by below code and please explain how?
StringBuilder s=new StringBuilder();
s.append("hi"+"java"+"test");
So from the msdn docs at the bottom of the page:
The following example illustrates how a StringBuilder object allocates
new memory and increases its capacity dynamically as the string
assigned to the object expands. The code creates a StringBuilder
object by calling its default (parameterless) constructor. The default
capacity of this object is 16 characters, and its maximum capacity is
more than 2 billion characters. Appending the string "This is a
sentence." results in a new memory allocation because the string
length (19 characters) exceeds the default capacity of the
StringBuilder object. The capacity of the object doubles to 32
characters, the new string is added, and the length of the object now
equals 19 characters. The code then appends the string "This is an
additional sentence." to the value of the StringBuilder object 11
times. Whenever the append operation causes the length of the
StringBuilder object to exceed its capacity, its existing capacity is
doubled and the Append operation succeeds.
I highlighted in bold the important parts.
So basically, the steps are:
StringBuilder creates a length to hold some strings
when the length is surpassed it will double it until it can hold the strings
Looking at line 373 in the source to output the string it will read through the memory addresses putting it together... from a very high level.
So thing to remember that it doesnt create objects, it's just dynamic reallocation of memory space to handle the new length of strings determined when append is called.
To Answer your question further
Scenario A), we set a length:
var s = new StringBuilder(10);
s.Append("hijavatest");
This should create a string builder of capacity 10, and store that string in it.
Scenario B), we don't specify a length:
var s = new StringBuilder();
s.Append("hi");
s.Append("java");
s.Append("test");
This will create the default capacity of a string builder: 16. and shouldn't change size to append all the new strings.
Scenario C), we set a small length:
var s = new StringBuilder(3);
s.Append("hi");
s.Append("java"); // doubles capacity to 6
s.Append("test"); // doubles capacity to 12
This will double the capacity till it can store all the string, in this case 12.
Run the below code in dotnetfiddle:
using System;
using System.Text;
public class Program
{
public static void Main()
{
ScenarioA();
ScenarioB();
ScenarioC();
}
private static void ShowSBInfo(StringBuilder sb)
{
foreach (var prop in sb.GetType().GetProperties()) {
if (prop.GetIndexParameters().Length == 0)
Console.Write("{0}: {1:N0} ", prop.Name, prop.GetValue(sb));
}
Console.WriteLine();
}
private static void ScenarioA()
{
Console.WriteLine("Scenario A");
var sb = new StringBuilder(10);
ShowSBInfo(sb);
sb.Append("hijavatest");
ShowSBInfo(sb);
}
private static void ScenarioB()
{
Console.WriteLine("Scenario B");
var sb = new StringBuilder();
ShowSBInfo(sb);
sb.Append("hi");
ShowSBInfo(sb);
sb.Append("java");
ShowSBInfo(sb);
sb.Append("test");
ShowSBInfo(sb);
}
private static void ScenarioC()
{
Console.WriteLine("Scenario C");
var sb = new StringBuilder(3);
ShowSBInfo(sb);
sb.Append("hi");
ShowSBInfo(sb);
sb.Append("java"); // doubles capacity to 6
ShowSBInfo(sb);
sb.Append("test"); // doubles capacity to 12
ShowSBInfo(sb);
}
}