1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.Collections.Generic; 6 using System.Collections.Immutable; 7 using System.Diagnostics; 8 using System.Reflection.Internal; 9 using System.Reflection.Metadata; 10 11 namespace System.Reflection.PortableExecutable 12 { 13 public abstract class PEBuilder 14 { 15 public PEHeaderBuilder Header { get; } 16 public Func<IEnumerable<Blob>, BlobContentId> IdProvider { get; } 17 public bool IsDeterministic { get; } 18 19 private readonly Lazy<ImmutableArray<Section>> _lazySections; 20 private Blob _lazyChecksum; 21 22 protected readonly struct Section 23 { 24 public readonly string Name; 25 public readonly SectionCharacteristics Characteristics; 26 SectionSystem.Reflection.PortableExecutable.PEBuilder.Section27 public Section(string name, SectionCharacteristics characteristics) 28 { 29 if (name == null) 30 { 31 Throw.ArgumentNull(nameof(name)); 32 } 33 34 Name = name; 35 Characteristics = characteristics; 36 } 37 } 38 39 private readonly struct SerializedSection 40 { 41 public readonly BlobBuilder Builder; 42 43 public readonly string Name; 44 public readonly SectionCharacteristics Characteristics; 45 public readonly int RelativeVirtualAddress; 46 public readonly int SizeOfRawData; 47 public readonly int PointerToRawData; 48 SerializedSectionSystem.Reflection.PortableExecutable.PEBuilder.SerializedSection49 public SerializedSection(BlobBuilder builder, string name, SectionCharacteristics characteristics, int relativeVirtualAddress, int sizeOfRawData, int pointerToRawData) 50 { 51 Name = name; 52 Characteristics = characteristics; 53 Builder = builder; 54 RelativeVirtualAddress = relativeVirtualAddress; 55 SizeOfRawData = sizeOfRawData; 56 PointerToRawData = pointerToRawData; 57 } 58 59 public int VirtualSize => Builder.Count; 60 } 61 PEBuilder(PEHeaderBuilder header, Func<IEnumerable<Blob>, BlobContentId> deterministicIdProvider)62 protected PEBuilder(PEHeaderBuilder header, Func<IEnumerable<Blob>, BlobContentId> deterministicIdProvider) 63 { 64 if (header == null) 65 { 66 Throw.ArgumentNull(nameof(header)); 67 } 68 69 IdProvider = deterministicIdProvider ?? BlobContentId.GetTimeBasedProvider(); 70 IsDeterministic = deterministicIdProvider != null; 71 Header = header; 72 _lazySections = new Lazy<ImmutableArray<Section>>(CreateSections); 73 } 74 GetSections()75 protected ImmutableArray<Section> GetSections() 76 { 77 var sections = _lazySections.Value; 78 if (sections.IsDefault) 79 { 80 throw new InvalidOperationException(SR.Format(SR.MustNotReturnNull, nameof(CreateSections))); 81 } 82 83 return sections; 84 } 85 CreateSections()86 protected abstract ImmutableArray<Section> CreateSections(); 87 SerializeSection(string name, SectionLocation location)88 protected abstract BlobBuilder SerializeSection(string name, SectionLocation location); 89 GetDirectories()90 protected internal abstract PEDirectoriesBuilder GetDirectories(); 91 Serialize(BlobBuilder builder)92 public BlobContentId Serialize(BlobBuilder builder) 93 { 94 // Define and serialize sections in two steps. 95 // We need to know about all sections before serializing them. 96 var serializedSections = SerializeSections(); 97 98 // The positions and sizes of directories are calculated during section serialization. 99 var directories = GetDirectories(); 100 101 Blob stampFixup; 102 WritePESignature(builder); 103 WriteCoffHeader(builder, serializedSections, out stampFixup); 104 WritePEHeader(builder, directories, serializedSections); 105 WriteSectionHeaders(builder, serializedSections); 106 builder.Align(Header.FileAlignment); 107 108 foreach (var section in serializedSections) 109 { 110 builder.LinkSuffix(section.Builder); 111 builder.Align(Header.FileAlignment); 112 } 113 114 var contentId = IdProvider(builder.GetBlobs()); 115 116 // patch timestamp in COFF header: 117 var stampWriter = new BlobWriter(stampFixup); 118 stampWriter.WriteUInt32(contentId.Stamp); 119 Debug.Assert(stampWriter.RemainingBytes == 0); 120 121 return contentId; 122 } 123 SerializeSections()124 private ImmutableArray<SerializedSection> SerializeSections() 125 { 126 var sections = GetSections(); 127 var result = ImmutableArray.CreateBuilder<SerializedSection>(sections.Length); 128 int sizeOfPeHeaders = Header.ComputeSizeOfPEHeaders(sections.Length); 129 130 var nextRva = BitArithmetic.Align(sizeOfPeHeaders, Header.SectionAlignment); 131 var nextPointer = BitArithmetic.Align(sizeOfPeHeaders, Header.FileAlignment); 132 133 foreach (var section in sections) 134 { 135 var builder = SerializeSection(section.Name, new SectionLocation(nextRva, nextPointer)); 136 137 var serialized = new SerializedSection( 138 builder, 139 section.Name, 140 section.Characteristics, 141 relativeVirtualAddress: nextRva, 142 sizeOfRawData: BitArithmetic.Align(builder.Count, Header.FileAlignment), 143 pointerToRawData: nextPointer); 144 145 result.Add(serialized); 146 147 nextRva = BitArithmetic.Align(serialized.RelativeVirtualAddress + serialized.VirtualSize, Header.SectionAlignment); 148 nextPointer = serialized.PointerToRawData + serialized.SizeOfRawData; 149 } 150 151 return result.MoveToImmutable(); 152 } 153 WritePESignature(BlobBuilder builder)154 private void WritePESignature(BlobBuilder builder) 155 { 156 // MS-DOS stub (128 bytes) 157 builder.WriteBytes(s_dosHeader); 158 159 // PE Signature "PE\0\0" 160 builder.WriteUInt32(PEHeaders.PESignature); 161 } 162 163 private static readonly byte[] s_dosHeader = new byte[] 164 { 165 0x4d, 0x5a, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00, 166 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 167 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 168 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 169 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 170 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 171 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 172 0x00, 0x00, 0x00, 0x00, 173 174 0x80, 0x00, 0x00, 0x00, // NT Header offset (0x80 == s_dosHeader.Length) 175 176 0x0e, 0x1f, 0xba, 0x0e, 0x00, 0xb4, 0x09, 0xcd, 177 0x21, 0xb8, 0x01, 0x4c, 0xcd, 0x21, 0x54, 0x68, 178 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 179 0x61, 0x6d, 0x20, 0x63, 0x61, 0x6e, 0x6e, 0x6f, 180 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x75, 0x6e, 181 0x20, 0x69, 0x6e, 0x20, 0x44, 0x4f, 0x53, 0x20, 182 0x6d, 0x6f, 0x64, 0x65, 0x2e, 0x0d, 0x0d, 0x0a, 183 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 184 }; 185 186 internal static int DosHeaderSize = s_dosHeader.Length; 187 WriteCoffHeader(BlobBuilder builder, ImmutableArray<SerializedSection> sections, out Blob stampFixup)188 private void WriteCoffHeader(BlobBuilder builder, ImmutableArray<SerializedSection> sections, out Blob stampFixup) 189 { 190 // Machine 191 builder.WriteUInt16((ushort)(Header.Machine == 0 ? Machine.I386 : Header.Machine)); 192 193 // NumberOfSections 194 builder.WriteUInt16((ushort)sections.Length); 195 196 // TimeDateStamp: 197 stampFixup = builder.ReserveBytes(sizeof(uint)); 198 199 // PointerToSymbolTable (TODO: not supported): 200 // The file pointer to the COFF symbol table, or zero if no COFF symbol table is present. 201 // This value should be zero for a PE image. 202 builder.WriteUInt32(0); 203 204 // NumberOfSymbols (TODO: not supported): 205 // The number of entries in the symbol table. This data can be used to locate the string table, 206 // which immediately follows the symbol table. This value should be zero for a PE image. 207 builder.WriteUInt32(0); 208 209 // SizeOfOptionalHeader: 210 // The size of the optional header, which is required for executable files but not for object files. 211 // This value should be zero for an object file (TODO). 212 builder.WriteUInt16((ushort)PEHeader.Size(Header.Is32Bit)); 213 214 // Characteristics 215 builder.WriteUInt16((ushort)Header.ImageCharacteristics); 216 } 217 WritePEHeader(BlobBuilder builder, PEDirectoriesBuilder directories, ImmutableArray<SerializedSection> sections)218 private void WritePEHeader(BlobBuilder builder, PEDirectoriesBuilder directories, ImmutableArray<SerializedSection> sections) 219 { 220 builder.WriteUInt16((ushort)(Header.Is32Bit ? PEMagic.PE32 : PEMagic.PE32Plus)); 221 builder.WriteByte(Header.MajorLinkerVersion); 222 builder.WriteByte(Header.MinorLinkerVersion); 223 224 // SizeOfCode: 225 builder.WriteUInt32((uint)SumRawDataSizes(sections, SectionCharacteristics.ContainsCode)); 226 227 // SizeOfInitializedData: 228 builder.WriteUInt32((uint)SumRawDataSizes(sections, SectionCharacteristics.ContainsInitializedData)); 229 230 // SizeOfUninitializedData: 231 builder.WriteUInt32((uint)SumRawDataSizes(sections, SectionCharacteristics.ContainsUninitializedData)); 232 233 // AddressOfEntryPoint: 234 builder.WriteUInt32((uint)directories.AddressOfEntryPoint); 235 236 // BaseOfCode: 237 int codeSectionIndex = IndexOfSection(sections, SectionCharacteristics.ContainsCode); 238 builder.WriteUInt32((uint)(codeSectionIndex != -1 ? sections[codeSectionIndex].RelativeVirtualAddress : 0)); 239 240 if (Header.Is32Bit) 241 { 242 // BaseOfData: 243 int dataSectionIndex = IndexOfSection(sections, SectionCharacteristics.ContainsInitializedData); 244 builder.WriteUInt32((uint)(dataSectionIndex != -1 ? sections[dataSectionIndex].RelativeVirtualAddress : 0)); 245 246 builder.WriteUInt32((uint)Header.ImageBase); 247 } 248 else 249 { 250 builder.WriteUInt64(Header.ImageBase); 251 } 252 253 // NT additional fields: 254 builder.WriteUInt32((uint)Header.SectionAlignment); 255 builder.WriteUInt32((uint)Header.FileAlignment); 256 builder.WriteUInt16(Header.MajorOperatingSystemVersion); 257 builder.WriteUInt16(Header.MinorOperatingSystemVersion); 258 builder.WriteUInt16(Header.MajorImageVersion); 259 builder.WriteUInt16(Header.MinorImageVersion); 260 builder.WriteUInt16(Header.MajorSubsystemVersion); 261 builder.WriteUInt16(Header.MinorSubsystemVersion); 262 263 // Win32VersionValue (reserved, should be 0) 264 builder.WriteUInt32(0); 265 266 // SizeOfImage: 267 var lastSection = sections[sections.Length - 1]; 268 builder.WriteUInt32((uint)BitArithmetic.Align(lastSection.RelativeVirtualAddress + lastSection.VirtualSize, Header.SectionAlignment)); 269 270 // SizeOfHeaders: 271 builder.WriteUInt32((uint)BitArithmetic.Align(Header.ComputeSizeOfPEHeaders(sections.Length), Header.FileAlignment)); 272 273 // Checksum: 274 // Shall be zero for strong name signing. 275 _lazyChecksum = builder.ReserveBytes(sizeof(uint)); 276 new BlobWriter(_lazyChecksum).WriteUInt32(0); 277 278 builder.WriteUInt16((ushort)Header.Subsystem); 279 builder.WriteUInt16((ushort)Header.DllCharacteristics); 280 281 if (Header.Is32Bit) 282 { 283 builder.WriteUInt32((uint)Header.SizeOfStackReserve); 284 builder.WriteUInt32((uint)Header.SizeOfStackCommit); 285 builder.WriteUInt32((uint)Header.SizeOfHeapReserve); 286 builder.WriteUInt32((uint)Header.SizeOfHeapCommit); 287 } 288 else 289 { 290 builder.WriteUInt64(Header.SizeOfStackReserve); 291 builder.WriteUInt64(Header.SizeOfStackCommit); 292 builder.WriteUInt64(Header.SizeOfHeapReserve); 293 builder.WriteUInt64(Header.SizeOfHeapCommit); 294 } 295 296 // LoaderFlags 297 builder.WriteUInt32(0); 298 299 // The number of data-directory entries in the remainder of the header. 300 builder.WriteUInt32(16); 301 302 // directory entries: 303 builder.WriteUInt32((uint)directories.ExportTable.RelativeVirtualAddress); 304 builder.WriteUInt32((uint)directories.ExportTable.Size); 305 builder.WriteUInt32((uint)directories.ImportTable.RelativeVirtualAddress); 306 builder.WriteUInt32((uint)directories.ImportTable.Size); 307 builder.WriteUInt32((uint)directories.ResourceTable.RelativeVirtualAddress); 308 builder.WriteUInt32((uint)directories.ResourceTable.Size); 309 builder.WriteUInt32((uint)directories.ExceptionTable.RelativeVirtualAddress); 310 builder.WriteUInt32((uint)directories.ExceptionTable.Size); 311 312 // Authenticode CertificateTable directory. Shall be zero before the PE is signed. 313 builder.WriteUInt32(0); 314 builder.WriteUInt32(0); 315 316 builder.WriteUInt32((uint)directories.BaseRelocationTable.RelativeVirtualAddress); 317 builder.WriteUInt32((uint)directories.BaseRelocationTable.Size); 318 builder.WriteUInt32((uint)directories.DebugTable.RelativeVirtualAddress); 319 builder.WriteUInt32((uint)directories.DebugTable.Size); 320 builder.WriteUInt32((uint)directories.CopyrightTable.RelativeVirtualAddress); 321 builder.WriteUInt32((uint)directories.CopyrightTable.Size); 322 builder.WriteUInt32((uint)directories.GlobalPointerTable.RelativeVirtualAddress); 323 builder.WriteUInt32((uint)directories.GlobalPointerTable.Size); 324 builder.WriteUInt32((uint)directories.ThreadLocalStorageTable.RelativeVirtualAddress); 325 builder.WriteUInt32((uint)directories.ThreadLocalStorageTable.Size); 326 builder.WriteUInt32((uint)directories.LoadConfigTable.RelativeVirtualAddress); 327 builder.WriteUInt32((uint)directories.LoadConfigTable.Size); 328 builder.WriteUInt32((uint)directories.BoundImportTable.RelativeVirtualAddress); 329 builder.WriteUInt32((uint)directories.BoundImportTable.Size); 330 builder.WriteUInt32((uint)directories.ImportAddressTable.RelativeVirtualAddress); 331 builder.WriteUInt32((uint)directories.ImportAddressTable.Size); 332 builder.WriteUInt32((uint)directories.DelayImportTable.RelativeVirtualAddress); 333 builder.WriteUInt32((uint)directories.DelayImportTable.Size); 334 builder.WriteUInt32((uint)directories.CorHeaderTable.RelativeVirtualAddress); 335 builder.WriteUInt32((uint)directories.CorHeaderTable.Size); 336 337 // Reserved, should be 0 338 builder.WriteUInt64(0); 339 } 340 WriteSectionHeaders(BlobBuilder builder, ImmutableArray<SerializedSection> serializedSections)341 private void WriteSectionHeaders(BlobBuilder builder, ImmutableArray<SerializedSection> serializedSections) 342 { 343 foreach (var serializedSection in serializedSections) 344 { 345 WriteSectionHeader(builder, serializedSection); 346 } 347 } 348 WriteSectionHeader(BlobBuilder builder, SerializedSection serializedSection)349 private static void WriteSectionHeader(BlobBuilder builder, SerializedSection serializedSection) 350 { 351 if (serializedSection.VirtualSize == 0) 352 { 353 return; 354 } 355 356 for (int j = 0, m = serializedSection.Name.Length; j < 8; j++) 357 { 358 if (j < m) 359 { 360 builder.WriteByte((byte)serializedSection.Name[j]); 361 } 362 else 363 { 364 builder.WriteByte(0); 365 } 366 } 367 368 builder.WriteUInt32((uint)serializedSection.VirtualSize); 369 builder.WriteUInt32((uint)serializedSection.RelativeVirtualAddress); 370 builder.WriteUInt32((uint)serializedSection.SizeOfRawData); 371 builder.WriteUInt32((uint)serializedSection.PointerToRawData); 372 373 // PointerToRelocations (TODO: not supported): 374 builder.WriteUInt32(0); 375 376 // PointerToLinenumbers (TODO: not supported): 377 builder.WriteUInt32(0); 378 379 // NumberOfRelocations (TODO: not supported): 380 builder.WriteUInt16(0); 381 382 // NumberOfLinenumbers (TODO: not supported): 383 builder.WriteUInt16(0); 384 385 builder.WriteUInt32((uint)serializedSection.Characteristics); 386 } 387 IndexOfSection(ImmutableArray<SerializedSection> sections, SectionCharacteristics characteristics)388 private static int IndexOfSection(ImmutableArray<SerializedSection> sections, SectionCharacteristics characteristics) 389 { 390 for (int i = 0; i < sections.Length; i++) 391 { 392 if ((sections[i].Characteristics & characteristics) == characteristics) 393 { 394 return i; 395 } 396 } 397 398 return -1; 399 } 400 SumRawDataSizes(ImmutableArray<SerializedSection> sections,SectionCharacteristics characteristics)401 private static int SumRawDataSizes(ImmutableArray<SerializedSection> sections,SectionCharacteristics characteristics) 402 { 403 int result = 0; 404 for (int i = 0; i < sections.Length; i++) 405 { 406 if ((sections[i].Characteristics & characteristics) == characteristics) 407 { 408 result += sections[i].SizeOfRawData; 409 } 410 } 411 412 return result; 413 } 414 415 // internal for testing GetContentToSign(BlobBuilder peImage, int peHeadersSize, int peHeaderAlignment, Blob strongNameSignatureFixup)416 internal static IEnumerable<Blob> GetContentToSign(BlobBuilder peImage, int peHeadersSize, int peHeaderAlignment, Blob strongNameSignatureFixup) 417 { 418 // Signed content includes 419 // - PE header without its alignment padding 420 // - all sections including their alignment padding and excluding strong name signature blob 421 422 // PE specification: 423 // To calculate the PE image hash, Authenticode orders the sections that are specified in the section table 424 // by address range, then hashes the resulting sequence of bytes, passing over the exclusion ranges. 425 // 426 // Note that sections are by construction ordered by their address, so there is no need to reorder. 427 428 int remainingHeaderToSign = peHeadersSize; 429 int remainingHeader = BitArithmetic.Align(peHeadersSize, peHeaderAlignment); 430 foreach (var blob in peImage.GetBlobs()) 431 { 432 int blobStart = blob.Start; 433 int blobLength = blob.Length; 434 while (blobLength > 0) 435 { 436 if (remainingHeader > 0) 437 { 438 int length; 439 440 if (remainingHeaderToSign > 0) 441 { 442 length = Math.Min(remainingHeaderToSign, blobLength); 443 yield return new Blob(blob.Buffer, blobStart, length); 444 remainingHeaderToSign -= length; 445 } 446 else 447 { 448 length = Math.Min(remainingHeader, blobLength); 449 } 450 451 remainingHeader -= length; 452 blobStart += length; 453 blobLength -= length; 454 } 455 else if (blob.Buffer == strongNameSignatureFixup.Buffer) 456 { 457 yield return GetPrefixBlob(new Blob(blob.Buffer, blobStart, blobLength), strongNameSignatureFixup); 458 yield return GetSuffixBlob(new Blob(blob.Buffer, blobStart, blobLength), strongNameSignatureFixup); 459 break; 460 } 461 else 462 { 463 yield return new Blob(blob.Buffer, blobStart, blobLength); 464 break; 465 } 466 } 467 } 468 } 469 470 // internal for testing GetPrefixBlob(Blob container, Blob blob)471 internal static Blob GetPrefixBlob(Blob container, Blob blob) => new Blob(container.Buffer, container.Start, blob.Start - container.Start); GetSuffixBlob(Blob container, Blob blob)472 internal static Blob GetSuffixBlob(Blob container, Blob blob) => new Blob(container.Buffer, blob.Start + blob.Length, container.Start + container.Length - blob.Start - blob.Length); 473 474 // internal for testing GetContentToChecksum(BlobBuilder peImage, Blob checksumFixup)475 internal static IEnumerable<Blob> GetContentToChecksum(BlobBuilder peImage, Blob checksumFixup) 476 { 477 foreach (var blob in peImage.GetBlobs()) 478 { 479 if (blob.Buffer == checksumFixup.Buffer) 480 { 481 yield return GetPrefixBlob(blob, checksumFixup); 482 yield return GetSuffixBlob(blob, checksumFixup); 483 } 484 else 485 { 486 yield return blob; 487 } 488 } 489 } 490 Sign(BlobBuilder peImage, Blob strongNameSignatureFixup, Func<IEnumerable<Blob>, byte[]> signatureProvider)491 internal void Sign(BlobBuilder peImage, Blob strongNameSignatureFixup, Func<IEnumerable<Blob>, byte[]> signatureProvider) 492 { 493 Debug.Assert(peImage != null); 494 Debug.Assert(signatureProvider != null); 495 496 int peHeadersSize = Header.ComputeSizeOfPEHeaders(GetSections().Length); 497 byte[] signature = signatureProvider(GetContentToSign(peImage, peHeadersSize, Header.FileAlignment, strongNameSignatureFixup)); 498 499 // signature may be shorter (the rest of the reserved space is padding): 500 if (signature == null || signature.Length > strongNameSignatureFixup.Length) 501 { 502 throw new InvalidOperationException(SR.SignatureProviderReturnedInvalidSignature); 503 } 504 505 var writer = new BlobWriter(strongNameSignatureFixup); 506 writer.WriteBytes(signature); 507 508 // Calculate the checksum after the strong name signature has been written. 509 uint checksum = CalculateChecksum(peImage, _lazyChecksum); 510 new BlobWriter(_lazyChecksum).WriteUInt32(checksum); 511 } 512 513 // internal for testing CalculateChecksum(BlobBuilder peImage, Blob checksumFixup)514 internal static uint CalculateChecksum(BlobBuilder peImage, Blob checksumFixup) 515 { 516 return CalculateChecksum(GetContentToChecksum(peImage, checksumFixup)) + (uint)peImage.Count; 517 } 518 CalculateChecksum(IEnumerable<Blob> blobs)519 private static unsafe uint CalculateChecksum(IEnumerable<Blob> blobs) 520 { 521 uint checksum = 0; 522 int pendingByte = -1; 523 524 foreach (var blob in blobs) 525 { 526 var segment = blob.GetBytes(); 527 fixed (byte* arrayPtr = segment.Array) 528 { 529 Debug.Assert(segment.Count > 0); 530 531 byte* ptr = arrayPtr + segment.Offset; 532 byte* end = ptr + segment.Count; 533 534 if (pendingByte >= 0) 535 { 536 // little-endian encoding: 537 checksum = AggregateChecksum(checksum, (ushort)(*ptr << 8 | pendingByte)); 538 ptr++; 539 } 540 541 if ((end - ptr) % 2 != 0) 542 { 543 end--; 544 pendingByte = *end; 545 } 546 else 547 { 548 pendingByte = -1; 549 } 550 551 while (ptr < end) 552 { 553 checksum = AggregateChecksum(checksum, *(ushort*)ptr); 554 ptr += sizeof(ushort); 555 } 556 } 557 } 558 559 if (pendingByte >= 0) 560 { 561 checksum = AggregateChecksum(checksum, (ushort)pendingByte); 562 } 563 564 return checksum; 565 } 566 AggregateChecksum(uint checksum, ushort value)567 private static uint AggregateChecksum(uint checksum, ushort value) 568 { 569 uint sum = checksum + value; 570 return (sum >> 16) + unchecked((ushort)sum); 571 } 572 } 573 } 574