/// <summary>BER encode class value.</summary> /// <param name="buffer">Target buffer. Value is appended to the end of it.</param> public override void Encode(MutableByte buffer) { MutableByte tmp = new MutableByte(); byte[] b = BitConverter.GetBytes(value); for (int i = 3; i >= 0; i--) { if (b[i] != 0 || tmp.Length > 0) { tmp.Append(b[i]); } } if (tmp.Length > 0 && (tmp[0] & 0x80) != 0) { tmp.Prepend(0); } else if (tmp.Length == 0) { tmp.Append(0); } BuildHeader(buffer, Type, tmp.Length); buffer.Append(tmp); }
/// <summary>Encode single OID instance value</summary> /// <param name="number">Instance value</param> /// <returns>Encoded instance value</returns> protected byte[] EncodeInstance(uint number) { MutableByte result = new MutableByte(); if (number <= 127) { result.Set((byte)number); } else { uint val = number; MutableByte tmp = new MutableByte(); while (val != 0) { byte[] b = BitConverter.GetBytes(val); byte bval = b[0]; if ((bval & 0x80) != 0) { bval = (byte)(bval & ~HighBit); // clear high bit } val >>= 7; // shift original value by 7 bits tmp.Append(bval); } // now we need to reverse the bytes for the final encoding for (int i = tmp.Length - 1; i >= 0; i--) { if (i > 0) { result.Append((byte)(tmp[i] | HighBit)); } else { result.Append(tmp[i]); } } } return(result); }
/// <summary>BER encode class value</summary> /// <param name="buffer">MutableByte to append BER encoded value to. /// </param> public override void Encode(MutableByte buffer) { byte[] b = BitConverter.GetBytes(value); MutableByte tmp = new MutableByte(); for (int i = b.Length - 1; i >= 0; i--) { if (b[i] != 0 || tmp.Length > 0) { tmp.Append(b[i]); } } if (tmp.Length == 0) { tmp.Append(0); // value is 0. can't have an empty encoding } BuildHeader(buffer, Type, tmp.Length); buffer.Append(tmp); }
/// <summary>BER encode OctetString variable.</summary> /// <param name="buffer"><see cref="MutableByte"/> encoding destination.</param> public override void Encode(MutableByte buffer) { if (data == null || data.Length == 0) { BuildHeader(buffer, Type, 0); } else { BuildHeader(buffer, Type, data.Length); buffer.Append(data); } }
/// <summary> /// Convert <see cref="ScopedPdu"/> into a BER encoded byte array. Resulting byte array is appended /// to the argument specified <see cref="MutableByte"/> class. /// /// Privacy operations are not performed by this method. Value encoded and returned by this method is /// suitable for sending in NoAuthNoPriv or AuthNoPriv security configurations. If privacy is required, /// caller will have to perform encryption and decryption operations after BER encoding is performed. /// /// In privacy protected SNMP version 3 packets, ScopedPdu is 1) encrypted using configured encryption /// method, 2) added to a <see cref="OctetString"/> field, and 3) appended to the data buffer. /// /// Because privacy operation is intrusive, it is recommended that BER encoding of the ScopedPdu packet /// is stored in a temporary <see cref="MutableByte"/> class, where it can be privacy protected and /// added to the <see cref="OctetString"/> class for final encoding into the target SNMP v3 packet. /// </summary> /// <param name="buffer"><see cref="MutableByte"/> class passed by reference that encoded ScopedPdu /// value is appended to.</param> public override void Encode(MutableByte buffer) { MutableByte tmp = new MutableByte(); contextEngineId.Encode(tmp); contextName.Encode(tmp); // Encode base base.Encode(tmp); BuildHeader(buffer, SnmpConstants.SmiSequence, tmp.Length); buffer.Append(tmp); }
/// <summary>Encodes ASN.1 object identifier and append it to the end of the passed buffer.</summary> /// <param name="buffer">Buffer to append the encoded information to.</param> public override void Encode(MutableByte buffer) { MutableByte tmpBuffer = new MutableByte(); uint[] values = data; if (values == null || values.Length < 2) { values = new uint[2]; values[0] = values[1] = 0; } // verify that it is a valid object id! if (values[0] < 0 || values[0] > 2) { throw new SnmpException("Invalid Object Identifier"); } if (values[1] < 0 || values[1] > 40) { throw new SnmpException("Invalid Object Identifier"); } // add the first oid! tmpBuffer.Append((byte)((values[0] * 40) + values[1])); // encode remaining instance values for (int i = 2; i < values.Length; i++) { tmpBuffer.Append(EncodeInstance(values[i])); } // build value header BuildHeader(buffer, Type, tmpBuffer.Length); // Append encoded value to the result buffer buffer.Append(tmpBuffer); }
/// <summary>BER encode sequence</summary> /// <param name="buffer">Target buffer</param> public override void Encode(MutableByte buffer) { int dataLen = 0; if (data != null && data.Length > 0) { dataLen = data.Length; } BuildHeader(buffer, Type, dataLen); if (dataLen > 0) { buffer.Append(data); } }
/// <summary>ASN.1 encode SNMP version 1 trap</summary> /// <param name="buffer"><see cref="MutableByte"/> buffer to the end of which encoded values are appended.</param> public override void encode(MutableByte buffer) { MutableByte trapBuffer = new MutableByte(); // encode the enterprise id & address _enterprise.encode(trapBuffer); _agentAddr.encode(trapBuffer); _generic.encode(trapBuffer); _specific.encode(trapBuffer); _timeStamp.encode(trapBuffer); _variables.encode(trapBuffer); MutableByte tmpBuffer = new MutableByte(); BuildHeader(tmpBuffer, (byte)PduType.Trap, trapBuffer.Length); trapBuffer.Prepend(tmpBuffer); buffer.Append(trapBuffer); }
/// <summary>BER encode security model field.</summary> /// <remarks> /// USM security model is a SEQUENCE encoded inside a OCTETSTRING. To encode it, first encode the sequence /// of class values then "wrap" it inside a OCTETSTRING field /// </remarks> /// <param name="buffer">Buffer to store encoded USM security model header</param> public override void Encode(MutableByte buffer) { MutableByte tmp = new MutableByte(); // First encode all the values that will form the sequence engineId.Encode(tmp); // Encode engine boots engineBoots.Encode(tmp); // encode engine time engineTime.Encode(tmp); securityName.Encode(tmp); if (authentication != AuthenticationDigests.None) { if (authenticationParameters.Length <= 0) { // If authentication is used, set authentication parameters field to 12 bytes set to 0x00 authenticationParameters.Set(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); } } else { authenticationParameters.Reset(); } authenticationParameters.Encode(tmp); if (privacy != EPrivacyProtocols.None) { if (privacyParameters.Length <= 0) { IPrivacyProtocol privProto = PrivacyProtocol.GetInstance(privacy); if (privProto != null) { byte[] parameter = new byte[privProto.PrivacyParametersLength]; for (int i = 0; i < privProto.PrivacyParametersLength; i++) { parameter[i] = 0x00; // This is not necessary since all array members are, by default, initialized to 0 } privacyParameters.Set(parameter); } else { throw new SnmpException(SnmpException.EErrorCode.UnsupportedPrivacyProtocol, "Unrecognized privacy protocol specified."); } } } else { privacyParameters.Reset(); } privacyParameters.Encode(tmp); MutableByte tmp1 = new MutableByte(); BuildHeader(tmp1, SnmpConstants.SmiSequence, tmp.Length); tmp1.Append(tmp); BuildHeader(buffer, (byte)EAsnType.OctetString, tmp1.Length); buffer.Append(tmp1); }
/// <summary>Decode BER encoded Oid value.</summary> /// <param name="buffer">BER encoded buffer</param> /// <param name="offset">The offset location to begin decoding</param> /// <returns>Buffer position after the decoded value</returns> public override int Decode(byte[] buffer, int offset) { byte asnType = ParseHeader(buffer, ref offset, out int headerLength); if (asnType != Type) { throw new SnmpException("Invalid ASN.1 type."); } // check for sufficient data if ((buffer.Length - offset) < headerLength) { throw new OverflowException("Buffer underflow error"); } if (headerLength == 0) { data = null; return(offset); } List <uint> list = new List <uint>(); // decode the first byte headerLength--; uint oid = Convert.ToUInt32(buffer[offset++]); list.Add(oid / 40); list.Add(oid % 40); // decode the rest of the identifiers while (headerLength > 0) { uint result = 0; // this is where we decode individual values { if ((buffer[offset] & HighBit) == 0) { // short encoding result = buffer[offset]; offset += 1; headerLength--; } else { // long encoding MutableByte tmp = new MutableByte(); bool completed = false; do { tmp.Append((byte)(buffer[offset] & ~HighBit)); if ((buffer[offset] & HighBit) == 0) { completed = true; } offset += 1; // advance offset --headerLength; // take out the processed byte from the header length } while (!completed); // convert byte array to integer for (int i = 0; i < tmp.Length; i++) { result <<= 7; result |= tmp[i]; } } } list.Add(result); } data = list.ToArray(); if (data.Length == 2 && data[0] == 0 && data[1] == 0) { data = null; } return(offset); }
/// <summary> /// Append BER encoded length to the <see cref="MutableByte"/> /// </summary> /// <param name="mb">MutableArray to append BER encoded length to</param> /// <param name="asnLength">Length value to encode.</param> /// <exception cref="ArgumentOutOfRangeException">Thrown when length value to encode is less then 0</exception> internal static void BuildLength(MutableByte mb, int asnLength) { if (asnLength < 0) throw new ArgumentOutOfRangeException("Length cannot be less then 0."); byte[] len = BitConverter.GetBytes(asnLength); MutableByte buf = new MutableByte(); for (int i = 3; i >= 0; i--) { if (len[i] != 0 || buf.Length > 0) buf.Append(len[i]); } if (buf.Length == 0) { // we are encoding a 0 value. Can't have a 0 byte length encoding buf.Append(0); } // check for short form encoding if (buf.Length == 1 && (buf[0] & HIGH_BIT) == 0) mb.Append(buf); // done else { // long form encoding byte encHeader = (byte)buf.Length; encHeader = (byte)(encHeader | HIGH_BIT); mb.Append(encHeader); mb.Append(buf); } }
/// <summary> /// Build ASN.1 header in the MutableByte array. /// </summary> /// <remarks> /// Header is the TL part of the TLV (type, length, value) BER encoded data representation. /// /// Each value is encoded as a Type byte, length of the data field and the actual, encoded /// data. This method will encode the type and length fields. /// </remarks> /// <param name="mb">MurableByte array</param> /// <param name="asnType">ASN.1 header type</param> /// <param name="asnLength">Length of the data contained in the header</param> internal static void BuildHeader(MutableByte mb, byte asnType, int asnLength) { mb.Append(asnType); BuildLength(mb, asnLength); }
/// <summary> /// Convert user password to acceptable authentication key. /// </summary> /// <param name="userPassword">User password</param> /// <param name="engineID">Authoritative engine id</param> /// <returns>Localized authentication key</returns> /// <exception cref="SnmpAuthenticationException">Thrown when key length is less then 8 bytes</exception> public byte[] PasswordToKey(byte[] userPassword, byte[] engineID) { // key length has to be at least 8 bytes long (RFC3414) if (userPassword == null || userPassword.Length < 8) throw new SnmpAuthenticationException("Secret key is too short."); int password_index = 0; int count = 0; SHA1 sha = new SHA1CryptoServiceProvider(); /* Use while loop until we've done 1 Megabyte */ byte[] sourceBuffer = new byte[1048576]; byte[] buf = new byte[64]; while (count < 1048576) { for (int i = 0; i < 64; ++i) { // Take the next octet of the password, wrapping // to the beginning of the password as necessary. buf[i] = userPassword[password_index++ % userPassword.Length]; } Buffer.BlockCopy(buf, 0, sourceBuffer, count, buf.Length); count += 64; } byte[] digest = sha.ComputeHash(sourceBuffer); MutableByte tmpbuf = new MutableByte(); tmpbuf.Append(digest); tmpbuf.Append(engineID); tmpbuf.Append(digest); byte[] res = sha.ComputeHash(tmpbuf); sha.Clear(); // release resources return res; }
/// <summary> /// BER encode sequence /// </summary> /// <param name="buffer">Target buffer</param> public override void encode(MutableByte buffer) { int dataLen = 0; if (_data != null && _data.Length > 0) dataLen = _data.Length; BuildHeader(buffer, Type, dataLen); if (dataLen > 0) buffer.Append(_data); }
/// <summary> /// Convert <see cref="ScopedPdu"/> into a BER encoded byte array. Resulting byte array is appended /// to the argument specified <see cref="MutableByte"/> class. /// /// Privacy operations are not performed by this method. Value encoded and returned by this method is /// suitable for sending in NoAuthNoPriv or AuthNoPriv security configurations. If privacy is required, /// caller will have to perform encryption and decryption operations after BER encoding is performed. /// /// In privacy protected SNMP version 3 packets, ScopedPdu is 1) encrypted using configured encryption /// method, 2) added to a <see cref="OctetString"/> field, and 3) appended to the data buffer. /// /// Because privacy operation is intrusive, it is recommended that BER encoding of the ScopedPdu packet /// is stored in a temporary <see cref="MutableByte"/> class, where it can be privacy protected and /// added to the <see cref="OctetString"/> class for final encoding into the target SNMP v3 packet. /// </summary> /// <param name="buffer"><see cref="MutableByte"/> class passed by reference that encoded ScopedPdu /// value is appended to.</param> public override void encode(MutableByte buffer) { MutableByte tmp = new MutableByte(); _contextEngineId.encode(tmp); _contextName.encode(tmp); // Encode base base.encode(tmp); BuildHeader(buffer, SnmpConstants.SMI_SEQUENCE, tmp.Length); buffer.Append(tmp); }
/// <summary>BER encode security model field.</summary> /// <remarks> /// USM security model is a SEQUENCE encoded inside a OCTETSTRING. To encode it, first encode the sequence /// of class values then "wrap" it inside a OCTETSTRING field /// </remarks> /// <param name="buffer">Buffer to store encoded USM security model header</param> public override void encode(MutableByte buffer) { MutableByte tmp = new MutableByte(); // First encode all the values that will form the sequence _engineId.encode(tmp); // Encode engine boots _engineBoots.encode(tmp); // encode engine time _engineTime.encode(tmp); _securityName.encode(tmp); if (_authentication != AuthenticationDigests.None) { if (_authenticationParameters.Length <= 0) { // If authentication is used, set authentication parameters field to 12 bytes set to 0x00 _authenticationParameters.Set(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); } } else { _authenticationParameters.Reset(); } _authenticationParameters.encode(tmp); if (_privacy != PrivacyProtocols.None) { if (_privacyParameters.Length <= 0) { IPrivacyProtocol privProto = PrivacyProtocol.GetInstance(_privacy); if (privProto != null) { byte[] parameter = new byte[privProto.PrivacyParametersLength]; for (int i = 0; i < privProto.PrivacyParametersLength; i++) { parameter[i] = 0x00; // This is not necessary since all array members are, by default, initialized to 0 } _privacyParameters.Set(parameter); } else throw new SnmpException(SnmpException.UnsupportedPrivacyProtocol, "Unrecognized privacy protocol specified."); } } else { _privacyParameters.Reset(); } _privacyParameters.encode(tmp); MutableByte tmp1 = new MutableByte(); BuildHeader(tmp1, SnmpConstants.SMI_SEQUENCE, tmp.Length); tmp1.Append(tmp); BuildHeader(buffer, OCTETSTRING, tmp1.Length); buffer.Append(tmp1); }
/// <summary> /// Convert user password to acceptable authentication key. /// </summary> /// <param name="userPassword">Authentication password</param> /// <param name="engineID">Authoritative engine id</param> /// <returns>Localized authentication key</returns> /// <exception cref="SnmpAuthenticationException">Thrown when key length is less then 8 bytes</exception> public byte[] PasswordToKey(byte[] userPassword, byte[] engineID) { // key length has to be at least 8 bytes long (RFC3414) if (userPassword == null || userPassword.Length < 8) throw new SnmpAuthenticationException("Secret key is too short."); int password_index = 0; int count = 0; MD5 md5 = new MD5CryptoServiceProvider(); byte[] sourceBuffer = new byte[1048576]; byte[] buf = new byte[64]; while (count < 1048576) { for (int i = 0; i < 64; ++i) { buf[i] = userPassword[password_index++ % userPassword.Length]; } Buffer.BlockCopy(buf, 0, sourceBuffer, count, buf.Length); count += 64; } byte[] digest = md5.ComputeHash(sourceBuffer); MutableByte tmpbuf = new MutableByte(); tmpbuf.Append(digest); tmpbuf.Append(engineID); tmpbuf.Append(digest); byte[] key = md5.ComputeHash(tmpbuf); return key; }
/// <summary> /// Encode SNMP version 3 packet /// </summary> /// <param name="authKey">Authentication key (not password)</param> /// <param name="privKey">Privacy key (not password)</param> /// <remarks> /// Before encoding the packet into a byte array you need to ensure all required information is /// set. Examples of required information is request type, Vbs (Oid + values pairs), USM settings including /// SecretName, authentication method and secret (if needed), privacy method and secret (if needed), etc. /// </remarks> /// <returns>Byte array BER encoded SNMP packet.</returns> public byte[] encode(byte[] authKey, byte[] privKey) { MutableByte buffer = new MutableByte(); // encode the global message data sequence header information MutableByte globalMessageData = new MutableByte(); // if message id is 0 then generate a new, random message id if (_messageId.Value == 0) { Random rand = new Random(); _messageId.Value = rand.Next(1, Int32.MaxValue); } // encode message id _messageId.encode(globalMessageData); // encode max message size _maxMessageSize.encode(globalMessageData); // message flags _msgFlags.encode(globalMessageData); // security model code _securityModel.Value = _userSecurityModel.Type; _securityModel.encode(globalMessageData); // add global message data to the main buffer // encode sequence header and add data AsnType.BuildHeader(buffer, SnmpConstants.SMI_SEQUENCE, globalMessageData.Length); buffer.Append(globalMessageData); MutableByte packetHeader = new MutableByte(buffer); // before going down this road, check if this is a discovery packet OctetString savedUserName = new OctetString(); bool privacy = _msgFlags.Privacy; bool authentication = _msgFlags.Authentication; bool reportable = _msgFlags.Reportable; if (_userSecurityModel.EngineId.Length <= 0) { // save USM settings prior to encoding a Discovery packet savedUserName.Set(_userSecurityModel.SecurityName); _userSecurityModel.SecurityName.Reset(); // delete security name for discovery packets _msgFlags.Authentication = false; _msgFlags.Privacy = false; _msgFlags.Reportable = true; } _userSecurityModel.encode(buffer); if (_userSecurityModel.EngineId.Length <= 0) { // restore saved USM values _userSecurityModel.SecurityName.Set(savedUserName); _msgFlags.Authentication = authentication; _msgFlags.Privacy = privacy; _msgFlags.Reportable = reportable; } // Check if privacy encryption is required MutableByte encodedPdu = new MutableByte(); if (_msgFlags.Privacy && _userSecurityModel.EngineId.Length > 0) { IPrivacyProtocol privacyProtocol = PrivacyProtocol.GetInstance(_userSecurityModel.Privacy); if (privacyProtocol == null) throw new SnmpException(SnmpException.UnsupportedPrivacyProtocol, "Specified privacy protocol is not supported."); // Get BER encoded ScopedPdu MutableByte unencryptedPdu = new MutableByte(); _scopedPdu.encode(unencryptedPdu); byte[] privacyParameters = null; // we have to expand the key IAuthenticationDigest auth = Authentication.GetInstance(_userSecurityModel.Authentication); if (auth == null) throw new SnmpException(SnmpException.UnsupportedNoAuthPriv, "Invalid authentication protocol. noAuthPriv mode not supported."); byte[] encryptedBuffer = privacyProtocol.Encrypt(unencryptedPdu, 0, unencryptedPdu.Length, privKey, _userSecurityModel.EngineBoots, _userSecurityModel.EngineTime, out privacyParameters, auth); _userSecurityModel.PrivacyParameters.Set(privacyParameters); OctetString encryptedOctetString = new OctetString(encryptedBuffer); encryptedOctetString.encode(encodedPdu); // now redo packet encoding buffer.Reset(); buffer.Set(packetHeader); _userSecurityModel.encode(buffer); int preEncodedLength = encodedPdu.Length; buffer.Append(encodedPdu); if (_maxMessageSize.Value != 0) { // verify compliance with maximum message size if ((encodedPdu.Length - preEncodedLength) > _maxMessageSize) { throw new SnmpException(SnmpException.MaximumMessageSizeExceeded, "ScopedPdu exceeds maximum message size."); } } } else { _scopedPdu.encode(encodedPdu); buffer.Append(encodedPdu); } base.encode(buffer); if (_msgFlags.Authentication && _userSecurityModel.EngineId.Length > 0) { _userSecurityModel.Authenticate(authKey, ref buffer); // Now re-encode the packet with the authentication information _userSecurityModel.encode(packetHeader); packetHeader.Append(encodedPdu); base.encode(packetHeader); buffer = packetHeader; } return buffer; }
/// <summary> /// Encode SNMP packet for sending. /// </summary> /// <returns>BER encoded SNMP packet.</returns> /// <exception cref="SnmpInvalidPduTypeException">Thrown when PDU being encoded is not a valid SNMP version 1 PDU. Acceptable /// protocol version 1 operations are GET, GET-NEXT, SET and RESPONSE.</exception> public override byte[] encode() { if (this.Pdu.Type != PduType.Get && this.Pdu.Type != PduType.GetNext && this.Pdu.Type != PduType.Set && this.Pdu.Type != PduType.Response) throw new SnmpInvalidVersionException("Invalid SNMP PDU type while attempting to encode PDU: " + string.Format("0x{0:x2}", this.Pdu.Type)); if (this.Pdu.RequestId == 0) { System.Random rand = new System.Random((System.Int32)DateTime.Now.Ticks); this.Pdu.RequestId = rand.Next(); } MutableByte tmpBuffer = new MutableByte(); // snmp version _protocolVersion.encode(tmpBuffer); // community string _snmpCommunity.encode(tmpBuffer); // pdu this.Pdu.encode(tmpBuffer); MutableByte buf = new MutableByte(); // wrap the packet into a sequence AsnType.BuildHeader(buf, SnmpConstants.SMI_SEQUENCE, tmpBuffer.Length); buf.Append(tmpBuffer); return buf; }
/// <summary>BER encode the variable binding /// </summary> /// <param name="buffer"><see cref="MutableByte"/> class to the end of which encoded variable /// binding values will be added. /// </param> public override void encode(MutableByte buffer) { // encode oid to the temporary buffer MutableByte oidbuf = new MutableByte(); _oid.encode(oidbuf); // encode value to a temporary buffer MutableByte valbuf = new MutableByte(); _value.encode(valbuf); // calculate data content length of the vb int vblen = oidbuf.Length + valbuf.Length; // encode vb header at the end of the result BuildHeader(buffer, Type, vblen); // add values to the encoded arrays to the end of the result buffer.Append(oidbuf); buffer.Append(valbuf); }
/// <summary> /// Used to encode the integer value into an ASN.1 buffer. /// The passed encoder defines the method for encoding the /// data. /// </summary> /// <param name="buffer">Buffer target to write the encoded data</param> public override void Encode(MutableByte buffer) { int val = value; byte[] b = BitConverter.GetBytes(value); MutableByte tmp = new MutableByte(); // if value is negative if (val < 0) { for (int i = 3; i >= 0; i--) { if (tmp.Length > 0 || b[i] != 0xff) { tmp.Append(b[i]); } } if (tmp.Length == 0) { // if the value is -1 then all bytes in an integer are 0xff and will be skipped above tmp.Append(0xff); } // make sure value is negative if ((tmp[0] & 0x80) == 0) { tmp.Prepend(0xff); } } else if (val == 0) { // this is just a shortcut to save processing time tmp.Append(0); } else { // byte[] b = BitConverter.GetBytes(val); for (int i = 3; i >= 0; i--) { if (b[i] != 0 || tmp.Length > 0) { tmp.Append(b[i]); } } // if buffer length is 0 then value is 0 and we have to add it to the buffer if (tmp.Length == 0) { tmp.Append(0); } else { if ((tmp[0] & 0x80) != 0) { // first bit of the first byte has to be 0 otherwise value is negative. tmp.Prepend(0); } } } // check for 9 1s at the beginning of the encoded value if (tmp.Length > 1 && tmp[0] == 0xff && (tmp[1] & 0x80) != 0) { tmp.Prepend(0); } BuildHeader(buffer, Type, tmp.Length); buffer.Append(tmp); }