From 41dfa438a6c270b4609a3904ccd65f044f1b0370 Mon Sep 17 00:00:00 2001 From: samatstarion Date: Wed, 11 Feb 2026 13:38:26 +0100 Subject: [PATCH] [Implement] kpar reading; fixes #64 --- README.md | 1 + .../Kernel_Data_Type_Library-1.0.0.kpar | Bin 0 -> 9102 bytes .../Kernel_Function_Library-1.0.0.kpar | Bin 0 -> 52809 bytes .../Kernel_Semantic_Library-1.0.0.kpar | Bin 0 -> 137407 bytes .../SysML_Analysis_Library-2.0.0.kpar | Bin 0 -> 16020 bytes .../SysML_Cause_and_Effect_Library-2.0.0.kpar | Bin 0 -> 6594 bytes .../SysML_Geometry_Library-2.0.0.kpar | Bin 0 -> 34334 bytes .../SysML_Metadata_Library-2.0.0.kpar | Bin 0 -> 9769 bytes ...ML_Quantities_and_Units_Library-2.0.0.kpar | Bin 0 -> 1014126 bytes ..._Requirement_Derivation_Library-2.0.0.kpar | Bin 0 -> 5024 bytes .../SysML_Systems_Library-2.0.0.kpar | Bin 0 -> 99387 bytes .../ArchiveExtensionsTestFixture.cs | 354 +++++++ .../ChecksumKindProviderTestFixture.cs | 232 ++++ .../Utilities/StreamExtensionsTestFixture.cs | 221 ++++ .../Utilities/StringExtensionsTestFixture.cs | 120 +++ .../ModelInterchange/ArchiveExtensions.cs | 147 +++ .../ModelInterchange/ChecksumKindProvider.cs | 264 +++++ .../Utilities/StreamExtensions.cs | 152 +++ .../Utilities/StringExtensions.cs | 58 + .../ArchiveSessionTestFixture.cs | 76 ++ .../Cryptography/Adler32TestFixture.cs | 103 ++ .../ChecksumServiceTestFixture.cs | 258 +++++ SysML2.NET.Kpar.Tests/ReaderTestFixture.cs | 350 ++++++ .../SysML2.NET.Kpar.Tests.csproj | 77 ++ SysML2.NET.Kpar.Tests/WriterTestFixture.cs | 34 + SysML2.NET.Kpar/ArchiveSession.cs | 144 +++ SysML2.NET.Kpar/ChecksumFailureBehavior.cs | 38 + .../ChecksumValidationException.cs | 54 + SysML2.NET.Kpar/Cryptography/Adler32.cs | 127 +++ .../Cryptography/ChecksumService.cs | 408 +++++++ .../Cryptography/IChecksumService.cs | 75 ++ SysML2.NET.Kpar/DictionaryExtensions.cs | 52 + SysML2.NET.Kpar/IReader.cs | 130 +++ SysML2.NET.Kpar/IWriter.cs | 78 ++ SysML2.NET.Kpar/ReadOptions.cs | 56 + SysML2.NET.Kpar/Reader.cs | 995 ++++++++++++++++++ SysML2.NET.Kpar/SysML2.NET.Kpar.csproj | 45 + SysML2.NET.Kpar/WriteOptions.cs | 48 + .../Data/.meta.json | 90 ++ .../Data/.project.json | 19 + ...erchangeChecksumDeserializerTestFixture.cs | 201 ++++ ...terchangeProjectDeSerializerTestFixture.cs | 88 ++ ...eProjectMetadataDeSerializerTestFixture.cs | 100 ++ .../SysML2.NET.Serializer.Json.Tests.csproj | 6 + .../Utf8JsonReaderHelperTestFixture.cs | 275 +++++ .../InterchangeChecksumDeserializer.cs | 134 +++ .../InterchangeProjectDeSerializer.cs | 233 ++++ .../InterchangeProjectMetadataDeSerializer.cs | 257 +++++ .../InterchangeProjectUsageDeSerializer.cs | 109 ++ .../Utility/Utf8JsonReaderHelper.cs | 171 +++ SysML2.NET.sln | 12 + SysML2.NET/ModelInterchange/Archive.cs | 59 ++ SysML2.NET/ModelInterchange/ChecksumKind.cs | 108 ++ .../ModelInterchange/ChecksumMismatch.cs | 52 + .../ModelInterchange/InterchangeChecksum.cs | 44 + .../ModelInterchange/InterchangeProject.cs | 65 ++ .../InterchangeProjectMetadata.cs | 86 ++ .../InterchangeProjectUsage.cs | 46 + SysML2.NET/ModelInterchange/ModelEntry.cs | 51 + SysML2.NET/ModelInterchange/ProjectBase.cs | Bin 0 -> 3562 bytes .../ModelInterchange/ProjectUsageBase.cs | 42 + 61 files changed, 6945 insertions(+) create mode 100644 Resources/sysml.library.kpar/Kernel_Data_Type_Library-1.0.0.kpar create mode 100644 Resources/sysml.library.kpar/Kernel_Function_Library-1.0.0.kpar create mode 100644 Resources/sysml.library.kpar/Kernel_Semantic_Library-1.0.0.kpar create mode 100644 Resources/sysml.library.kpar/SysML_Analysis_Library-2.0.0.kpar create mode 100644 Resources/sysml.library.kpar/SysML_Cause_and_Effect_Library-2.0.0.kpar create mode 100644 Resources/sysml.library.kpar/SysML_Geometry_Library-2.0.0.kpar create mode 100644 Resources/sysml.library.kpar/SysML_Metadata_Library-2.0.0.kpar create mode 100644 Resources/sysml.library.kpar/SysML_Quantities_and_Units_Library-2.0.0.kpar create mode 100644 Resources/sysml.library.kpar/SysML_Requirement_Derivation_Library-2.0.0.kpar create mode 100644 Resources/sysml.library.kpar/SysML_Systems_Library-2.0.0.kpar create mode 100644 SysML2.NET.Extensions.Tests/ModelInterchange/ArchiveExtensionsTestFixture.cs create mode 100644 SysML2.NET.Extensions.Tests/ModelInterchange/ChecksumKindProviderTestFixture.cs create mode 100644 SysML2.NET.Extensions.Tests/Utilities/StreamExtensionsTestFixture.cs create mode 100644 SysML2.NET.Extensions.Tests/Utilities/StringExtensionsTestFixture.cs create mode 100644 SysML2.NET.Extensions/ModelInterchange/ArchiveExtensions.cs create mode 100644 SysML2.NET.Extensions/ModelInterchange/ChecksumKindProvider.cs create mode 100644 SysML2.NET.Extensions/Utilities/StreamExtensions.cs create mode 100644 SysML2.NET.Extensions/Utilities/StringExtensions.cs create mode 100644 SysML2.NET.Kpar.Tests/ArchiveSessionTestFixture.cs create mode 100644 SysML2.NET.Kpar.Tests/Cryptography/Adler32TestFixture.cs create mode 100644 SysML2.NET.Kpar.Tests/Cryptography/ChecksumServiceTestFixture.cs create mode 100644 SysML2.NET.Kpar.Tests/ReaderTestFixture.cs create mode 100644 SysML2.NET.Kpar.Tests/SysML2.NET.Kpar.Tests.csproj create mode 100644 SysML2.NET.Kpar.Tests/WriterTestFixture.cs create mode 100644 SysML2.NET.Kpar/ArchiveSession.cs create mode 100644 SysML2.NET.Kpar/ChecksumFailureBehavior.cs create mode 100644 SysML2.NET.Kpar/ChecksumValidationException.cs create mode 100644 SysML2.NET.Kpar/Cryptography/Adler32.cs create mode 100644 SysML2.NET.Kpar/Cryptography/ChecksumService.cs create mode 100644 SysML2.NET.Kpar/Cryptography/IChecksumService.cs create mode 100644 SysML2.NET.Kpar/DictionaryExtensions.cs create mode 100644 SysML2.NET.Kpar/IReader.cs create mode 100644 SysML2.NET.Kpar/IWriter.cs create mode 100644 SysML2.NET.Kpar/ReadOptions.cs create mode 100644 SysML2.NET.Kpar/Reader.cs create mode 100644 SysML2.NET.Kpar/SysML2.NET.Kpar.csproj create mode 100644 SysML2.NET.Kpar/WriteOptions.cs create mode 100644 SysML2.NET.Serializer.Json.Tests/Data/.meta.json create mode 100644 SysML2.NET.Serializer.Json.Tests/Data/.project.json create mode 100644 SysML2.NET.Serializer.Json.Tests/ModelInterchange/InterchangeChecksumDeserializerTestFixture.cs create mode 100644 SysML2.NET.Serializer.Json.Tests/ModelInterchange/InterchangeProjectDeSerializerTestFixture.cs create mode 100644 SysML2.NET.Serializer.Json.Tests/ModelInterchange/InterchangeProjectMetadataDeSerializerTestFixture.cs create mode 100644 SysML2.NET.Serializer.Json.Tests/Utility/Utf8JsonReaderHelperTestFixture.cs create mode 100644 SysML2.NET.Serializer.Json/ModelInterchange/InterchangeChecksumDeserializer.cs create mode 100644 SysML2.NET.Serializer.Json/ModelInterchange/InterchangeProjectDeSerializer.cs create mode 100644 SysML2.NET.Serializer.Json/ModelInterchange/InterchangeProjectMetadataDeSerializer.cs create mode 100644 SysML2.NET.Serializer.Json/ModelInterchange/InterchangeProjectUsageDeSerializer.cs create mode 100644 SysML2.NET.Serializer.Json/Utility/Utf8JsonReaderHelper.cs create mode 100644 SysML2.NET/ModelInterchange/Archive.cs create mode 100644 SysML2.NET/ModelInterchange/ChecksumKind.cs create mode 100644 SysML2.NET/ModelInterchange/ChecksumMismatch.cs create mode 100644 SysML2.NET/ModelInterchange/InterchangeChecksum.cs create mode 100644 SysML2.NET/ModelInterchange/InterchangeProject.cs create mode 100644 SysML2.NET/ModelInterchange/InterchangeProjectMetadata.cs create mode 100644 SysML2.NET/ModelInterchange/InterchangeProjectUsage.cs create mode 100644 SysML2.NET/ModelInterchange/ModelEntry.cs create mode 100644 SysML2.NET/ModelInterchange/ProjectBase.cs create mode 100644 SysML2.NET/ModelInterchange/ProjectUsageBase.cs diff --git a/README.md b/README.md index 7fbf263ba..8f24aa60a 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ project [SysML2.NET.Extensions](https://www.nuget.org/packages/SysML2.NET.Extensions) | ![NuGet Version](https://img.shields.io/nuget/v/SysML2.NET.Extensions) [SysML2.NET.Serializer.Json](https://www.nuget.org/packages/SysML2.NET.Serializer.Json) | ![NuGet Version](https://img.shields.io/nuget/v/SysML2.NET.Serializer.Json) [SysML2.NET.Serializer.MessagePack](https://www.nuget.org/packages/SysML2.NET.Serializer.MessagePack) | ![NuGet Version](https://img.shields.io/nuget/v/SysML2.NET.Serializer.MessagePack) +[SysML2.NET.Kpar](https://www.nuget.org/packages/SysML2.NET.Kpar) | ![NuGet Version](https://img.shields.io/nuget/v/SysML2.NET.Kpar) [SysML2.NET.REST](https://www.nuget.org/packages/SysML2.NET.REST) | ![NuGet Version](https://img.shields.io/nuget/v/SysML2.NET.REST) [SysML2.NET.DAL](https://www.nuget.org/packages/SysML2.NET.DAL) | ![NuGet Version](https://img.shields.io/nuget/v/SysML2.NET.DAL) diff --git a/Resources/sysml.library.kpar/Kernel_Data_Type_Library-1.0.0.kpar b/Resources/sysml.library.kpar/Kernel_Data_Type_Library-1.0.0.kpar new file mode 100644 index 0000000000000000000000000000000000000000..c944f3ae187b9a25ebd7614ae941c2f9fc61f9d5 GIT binary patch literal 9102 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 S!B6ST2l#IY!9MHKr~d=6jcGgp literal 0 HcmV?d00001 diff --git a/Resources/sysml.library.kpar/Kernel_Function_Library-1.0.0.kpar b/Resources/sysml.library.kpar/Kernel_Function_Library-1.0.0.kpar new file mode 100644 index 0000000000000000000000000000000000000000..beaf224a02374742a79be77ed13ae3d6a98ab2a8 GIT binary patch literal 52809 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 Y7_n6IAxI3F{!H`I>85$&L#Oc9+cj<&P5=M^ literal 0 HcmV?d00001 diff --git a/Resources/sysml.library.kpar/SysML_Analysis_Library-2.0.0.kpar b/Resources/sysml.library.kpar/SysML_Analysis_Library-2.0.0.kpar new file mode 100644 index 0000000000000000000000000000000000000000..fcdf17b764afd432e3e70c83ab748ef8cdb760ac GIT binary patch literal 16020 zcmcIr&2JpZbsu|e10-NL2pl5^!zj}SlG>T!ki*Z>l7=F!9YNBrEGjbK#U5(9Ysj`{ zy2t&&k0p5UC4WHfx%prqCm(#v(a2vAAPA644mstNTP^|ed#^sbr-w`J%35(X?5=wC z>b>9lteTh4KKRyZNB{Y8=Rg1Z^MCxqH#;5r`7R#z=Xv&P6_x#87g>6-KSmv>ic4VB*WRi-ho**V!F3NKy zK(AD@LcGXgHBHhp@m!{7l{`~ogO}O_=?j$?n(R)0&>!GQRba&0=(C%(ToqZBN5tBB zSzT@5Jm1_FL>MiN(()arR=@Fo~u9mN?DoV)!OK0&4T)MmJt%6 z{l=4iab3)&K+36{&8I51QZESbjYocLDNA)amyvp<=0K}T@xU2L>nk_+buMFdT2?XS zfHlHj&6wNnT9hlS5aT@|yf+y3wui3=gAxDz6}Y9n&d9tlWT}nR^^953xr*Kv)r=$q zYrcz7jJS}~ifnLrcqI1*YN(_f9`44wlf(FMXE+@0#qptxVi}F&czbvh9S-(}^)d^cY8Y?jQ6$=;+>;Ot^5dfLqC57+i4X^Vg0@J?*RN;^5Tur;%0Rv&Z5U5 zjv!*kMj31)NecljC9rr{Nx^}F5FEpqBATR`HlIS|YD&0gGG*oiO$qZA@ZRbyXG81L zh^^Jtm3f|AV1`I$^DHmLX(XpIe?!16MxzH0S65b7+(O_$kRSN?N<|ehLX2z9nRVv9 z_TEiRVwiX`fysg=p{`1mBL+731H)qliV#6ns94~4ur*aP%;mUfc8XV$Jv(;6+W?a* zvMlptTwyD;*G~|b#YmjuS$c+D1h1<+3E$kdZt;!G6FHu$B|CJl{RRuoGom>~aMBuf zDY6OeS96OOvJ~gC&|4fUg*RGv*wH1dyCGdmyf2=X0+6smv6D0%38>`QE<`GjKV(=w z7ZbR@Dp#~Vv11tEHRyNBTg*F6s^gK&hD@@XIt_he0+cm<= z1==8)Hw^0HU;q8ZKm28^s>oB6o@m|9;T5jRI$u<{su2cqetm=d9FeCrMA^Nzy(lke~mFn#L7IIHU_P( z$T8(-Q7K@+5%~)7F@+yCc00HQ%#YHxanQMBLKVCT**POYX@V?2CvymA%EnwK7pkkV z06E81M0u+W9g3zB!5+qBATz)gu1W)+5M}7odVnqC*dW}mK*Dz>53J_cXutpr+sdCe zYn+qzPVAn@7(6+GHkh$Hi;21>zj7<^8GmyHLxQ0?_X0bprX{%$?<+ss{=9v?N8u=N zYmyz3&El5n$r;3m)jW$0me^f6W#{TQRg!B5$==!KM%SN?sZ0T}Hl+)%K0l5O9Sn?t z;vm+znj~`s$)^Prv_=rL6!z!YF@EDb%Rq0EXERLFn=6#|i++!Kljt0ZhhqF2$0^w0JupuI^CUIr$V&GZEdc2lQ))RL~naow5)Xm+xxfq)3kl zA9dgx32OXzE#gs?!F2U-omumW?nusfF*s;|>nLt0NWX2Q<@9u`77YEgJY zvBpcY=V2_B+@}yXtdorvHaErbF|Y@j!<({04ILNx(HF?HXUP}JgKTut#!9PmU7QSQ zhaS^YW#O8|3BJ$_i55Zwpj$Xc_6UzoLuSNbHCr@TqahClO>7Yn+HY^sYy5aHj!sUr zaE@!n+B@MTA(VD({tUUNTojzf9Sd7yz?iA!KTB1hPcTCM-E%JVpbWatTUA-R_8SdXfMJ`B6*Wsj^(PbsA_7c1%9Z*qZ8mW|Si_3G$ zAZ&zWEwyA2kPl2|0|qoz5|$07H$e#LB~Ld=o0&~{hD|f!5!t2EB4KdS=D}%A_*h6( zRmS-uj8ZZXV=-4>( z%yxBTv~IQ4$80(~#~dp~E^7kE{Vox_MfghsIkxCI?%OyGHb_-ZNoM{v5)yPqx?+sZ zBODy2+7cFVmV|-Yrb5_`vZ>deAyw0l4$D5L2)qlt2qiEMq%Ij~lu7S1H2^ynEqWV1 zxwR>Q70eQeATy=T54ruiR7^R`WKq?k4nW_BWlI~grP~JlG7A;(0yFixj)e{aKwq4p zuq!f#OFd8VFf5%RJD4MF@k;bhsWH@Ae0gGQb#;gs_RT-9mTjD38Ci=A>lB@*aN%n5 zZ9}G>buY^)VCWD66{iY~7<5pOI>Uz;%WEZt9-VF|m>L0lF-^NJHDaWgW$+ce{3MB} zdz1GJA+1qz9JS~tfxTZ9H~$1~<>0-;hITX4c7S%*a< zjis2{w`KVt;|GxichWo2b3TOxZxGPAOm7cLugSK(QMkQ|@wG<0hgTQQoGJkbn%yJY z1hh6&s^mvTx?wiQD1+@RA~$plZSUJMN9sgLzCE6|j_ zP=L)stIE1+w345x>o?R@ekqguONM@$b2HJ{b;I zA5EwC8EIn7eTHh7W8^BCu1}(=h|n6P4GUBcp#WOapkOfIEyY`P-O~y4oCrX#@OFG! zY#vi?BSnsbVWppRb$=LDKH8hCc!7or_s(H~n!Y=o^GShqIRWpy3~LLFpAah#d3*IO z^3r}3nAM|hajFmybAoVdlQg`J8?s<_9yHk{lb5J$bW^@ZDkmSS+cve7K&*R7u3G_^ z(#o~ph}fA*Rh~rM7JKO;A{#!cG=X%nEZR5l=`(cx{DW4rqPiCiFJol3GlW=>mRU;c znfQWsZ4#9*(uBH0lG`y@!9I2z#LB*g&J0)X1I`6za*nehWAg?gx(39qGyjIwWnOqn z#H+c4U_7Pc93poDqfD3FF;)HA011)ndM0CFogUy!@4T)6Aa7lRu~}_xM zPP7k0YXfpk9t!)iA1LA{8_A~l$asLF@#e$r%}sGjNEYCiPRAM$baqZi`+7VeZDFY{ z;ei$idGI*L_K|;Z$f;&E*NV@khe|G-dvW3R}8L!qG6*q45D z*S5S7yyLD2#$S6^e)WY-X-U;QyHS>TV|TS6c~}h+IwLsp(tI2e)-xF{KH){E;!wQ{ zs$&R+1lV}cVWJ$Dlkg3?IqZXB6Ugd0DsYKJt|sCekhfN= zb+9;tGMD9wkrXTlRT7r~c?BCXC{xe!8S2pTl5PrUxe#8OXP|x$?mojKiB^ zOKdE8Cwd}aq&ACLHqOYjGmmS7y+alsiV1Z?!$pF(^)>v9&4&;Q$(Otj{?-=@RhvxU zM{q-s4pP`a;U3%rPFrd^|0xe5k*g`%te73XhN6iUWi2g_k+S^M=oc`wG1boL265Ir z!-TE|&8AyX<706?k8PvMDzpx`QRx5Q6I`(&Z8}$u$TlRkNPL z+!K3x`is%%Nu@87y`lJ_i@(M&e`9qtdV{8WmYXNK8PGaMk9+J}nEE1`F0kz_7jJP~ zX**F;ZMk$I*Z8f^e#%knD@??3EXBKJGwU2XU-ffUeuArEj2cS(NV?>TICxTg0ZrtV z_Ax7yugcN|Dkzpe2LsAvhU>6uJ`#0(7!t;aYm=Gy?Q;Ii#%>%F@5Y;+<`&k};J)Bi z_?7UA(C0999e#`(Fy#|?b+bMh14{$fBl%yrHXg$TF8Z{&iWKrhqz2~o7Jo1s^h^9o z<=NvrE2!3Xi$Hr`toG@{Ri=>O7Cu#K!HB2y=nlsCJ!MO$r?03+O!ZMylQaJPZZSN$ z#X3A|ByP|MY7c+#?_F-{uu-fGx@v^apL4O(BIYGLX(4_-6Y5tQH@8pxVZ!*GHs5*4Z@k81G~P+%a_x<;riEYB0>l83fUye8jTnc zmrek$G z7$K*pwC6>En?VE=5>SpTuNgHRv0Cz5C*=Z}Q;xY`P3n1!94bkxtSXoZHa+K@Yr)1n zy%XSfP;>2BzQt%(MZt(mLP?f;J;mN5q)MUuhE~n?Bs=bg&j=xr^l_7morR;p%ds za-aGKnymgmvjdE4yV5njHL-9UeFn&CFoplPOXZ_cZL~+RrEAd)IzJBs-iSTWlJOqo z3)8&vTIYt?UzkfTDV40v)V|uVTeAwxcS7si{)yd}aqjf1qJ&OlQB>he1US4CUo;c3 z!n93CqBTdmaoVD`?_J`?aR@HlN)$A_h=@$OM@<;Ckf+zAd+oD{vwOs$WHLdBB(60V zx;CT9K_2GfQ0#{z_aOsQd9@)2CuO6uqSplw;X> zD5PiOB~J9c1R?fUZSf~?aZk)?)BA96chCyln>*iXac)lHxVGK!Zgp*NTI_=%CFy)% zWo3JR2j`a54$l4R?dIJboKH82>oDNJ%*cnhRhA~51WNkTG|~WuAL2l8GVy#`1Grs% z`Rp6tT>W7AH=ohy;6K0nE&Wsb9&~eR^NXc=VyE-F-~I=_M(w|5+ML(?Qk|do*WX$4 z6}u*h=GTZkiDaeIaaT$3Jv9AY(dPW-B@RFT!yhiY($S>R><@Vw-|v1|x9H`wZ)2;S S&Uf(N-+!ai`QMM|xBmxr(bz-) literal 0 HcmV?d00001 diff --git a/Resources/sysml.library.kpar/SysML_Cause_and_Effect_Library-2.0.0.kpar b/Resources/sysml.library.kpar/SysML_Cause_and_Effect_Library-2.0.0.kpar new file mode 100644 index 0000000000000000000000000000000000000000..75da9ef1d852df213aa8055d8f77e54408002785 GIT binary patch literal 6594 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 YYFYi_+0TAXwvX`Nzw!O=zo6g#0}w+y@&Et; literal 0 HcmV?d00001 diff --git a/Resources/sysml.library.kpar/SysML_Geometry_Library-2.0.0.kpar b/Resources/sysml.library.kpar/SysML_Geometry_Library-2.0.0.kpar new file mode 100644 index 0000000000000000000000000000000000000000..1b8438615de9d1786cb6af40d9e66ce051338e33 GIT binary patch literal 34334 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 YD}SFneD^(~dk_Ep2I~6VkLc5X0Ryd%6#xJL literal 0 HcmV?d00001 diff --git a/Resources/sysml.library.kpar/SysML_Quantities_and_Units_Library-2.0.0.kpar b/Resources/sysml.library.kpar/SysML_Quantities_and_Units_Library-2.0.0.kpar new file mode 100644 index 0000000000000000000000000000000000000000..17f566bb6290e022af597c166bf42322887381ca GIT binary patch literal 1014126 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%0AED^f#ic4|JG< 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 TI)`2V3v);K?T>n35_A7QQ+Y2V literal 0 HcmV?d00001 diff --git a/Resources/sysml.library.kpar/SysML_Requirement_Derivation_Library-2.0.0.kpar b/Resources/sysml.library.kpar/SysML_Requirement_Derivation_Library-2.0.0.kpar new file mode 100644 index 0000000000000000000000000000000000000000..cd1ba93ba12ebf0b94145d990335baa3e6431d63 GIT binary patch literal 5024 zcmb_gTW=dh6n1HgWJ`d=BM52mQyV4r`j(4}JQCb=nk?IC0iIC0^He7Bc{}EjXph@R3we0J9*du{AQh2_6?-7k;FPN%iP=Fg zNqM?xueUuR-87ifRT5G>_IT>Cw3L&~D+RqI;z7(pBhUOKWojlEj42UGc9?h~4B{y} z;PEu$Q^D?OKkmcQsYs=<((gDO2UjwQ6YcTIc{>#{$x@g6o+&k#<8Jrt?5vYS(@v63 zyK*kv?!HJ554wG)KXUp`uX`jS9;?9J$Ybd|oAb0Z^JNlCmBLpAojq;vV*RZQeVxTF z!Snwc($H8D84%yZjzRF^$;G3k%6R%~cJVu^f?iZFok%F2mqieJ;(2>~-Y&}k7{#a? z#3lh!?e&ga$S4G?Rw0!KAys=lm%3PQyQ$zxcsNLj+i(UOy}_~LjP=it&`TahR5duV zGWE+qLCT#8_nFKhisw2H@bM`RGpZ{gMib%nCtKX>ZHbY)HF8E`b8EBb^)`p1H}u57 zbA~-{XG;txzVABDXnQo^y@4Z~9p0yU<>53*12u!$_R%+c{n2I{A(n7hc;*OL_J@wx zp7aL8(YD*`I}@J|{T)7ZMmrPV9qw%Rg}*b}+3tiQ#&_!|p$6L)yZ#?0%&ymS#5c6=CeV4zp!Y<_4c$Zam5fFp~ zes|aKi>+O&0oZ`1`~V1XrWucMyp37mfQ?7T=zFLb5C(S%G_A)}!X^T;=B?sW&AD~* z3<50iVX3T2qZ8OR8D0p8$8n;lE58cOU>FmBA0CXy-xDDNE1cS86E4N^VlLPhm#$g} zNuAuP;Kfy(JsKWj*El3ROT`UA))Cb!Sb?#rE}&*r+)WTRt}lrb!16yMjAE=poZGVC zxf#NGuIDK0_iuylvLH(EO6}9Yw~LFpmowxBbyoKB1K|g8KqJVM-8IEo3au(rjiHWV z1$NC=g)P_qdG-FSW}`tbVoP0PT0?0~8ZKYSmbEfDTedVsfFbHBlV2`UnjAGvmO)t9 zN)UiL6C$=tSpsoQn&|5a86JURMeK@VcL61e=WdvRr-5_jFhW&n5?W_@7#^n?F+rfDJjg%GVWhSA zP3*Utd_vo#t1S1xF23#fIhuNTEbQ4IyO)gu{+A+!VKw85C9wc#EJ}dUD$fhA{WOVS z1@*+4#3&6()Qm9;?~bXqNboqf!Q%y!nI4YDs6>rRJ(IA#8;v!Jn4Be9=&e(R0wqQ0 zlcx$7r!$s7lKz1k5^#nzP-s#GYMmMN3!_+Y7pHw3tqm|IWOH`U#@7r>g%RYj$->4m z%A{g5ek$yCFvC^qCPD#|$C|Bk1II9*V@5-=xN}(@HX$Yj5_A>eb#^uj!W=k?3->QL zXkye8!XQ_usL5-|mWaIDW#1-Bj7{QFfy&BRk+~dmpH>GikP295ft&@CAPm%kVfC6| zgzH!)*k#g%1)p38XSfhH`f+oFo26imdnVc~c!N$&~Pw%xkCN!sTuCp2*tPFe+0XtjOGT_HcJPLQ>l6rc1u9gyQ|Oxpc#hYrBN z4#Zn__iGqm;~qRL7~U`qRYiU?e2EI`?SbCRyelBosb@Mn*c)jRMKR{0mcuzdl%!@2 z7#q9A0J>M`e_Y-N+=o=2;n%zVFhW5JdBuIQj0r@4z>q)8L@e;kVF@wIBy~YSgP=HK zjMXG%!snq(a_vCjQkYu83}UllH){gyfFaOIbhARflR28O_iB*Jk7`JOCSX}Leu~FA zC~J!y>!xA86d`14L)z?_j~nw0)kS@;q10zA0?GNb7{b_`ZoSjGz4|c})+hb-@&)}_ zdMH&Dtv;tPf{n(@7azT2KeedJRc~%fxleGrYbCb~$SiMFMb*8pRJ4yxu#~>C1wPup S`z{^6gYUa|%JTDv^xwZxaBZIe literal 0 HcmV?d00001 diff --git a/Resources/sysml.library.kpar/SysML_Systems_Library-2.0.0.kpar b/Resources/sysml.library.kpar/SysML_Systems_Library-2.0.0.kpar new file mode 100644 index 0000000000000000000000000000000000000000..5af313598b72976a191df451a66660f438e37ee2 GIT binary patch literal 99387 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 literal 0 HcmV?d00001 diff --git a/SysML2.NET.Extensions.Tests/ModelInterchange/ArchiveExtensionsTestFixture.cs b/SysML2.NET.Extensions.Tests/ModelInterchange/ArchiveExtensionsTestFixture.cs new file mode 100644 index 000000000..e172b419a --- /dev/null +++ b/SysML2.NET.Extensions.Tests/ModelInterchange/ArchiveExtensionsTestFixture.cs @@ -0,0 +1,354 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------- + +namespace SysML2.NET.ModelInterchange.Tests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + + using NUnit.Framework; + + using SysML2.NET.Extensions.ModelInterchange; + + [TestFixture] + public class ArchiveExtensionsTestFixture + { + [Test] + public void Verify_that_TryGetModelEntryByIndexKey_throws_when_archive_is_null() + { + Assert.That(() => + { + ArchiveExtensions.TryGetModelEntryByIndexKey(null, "Base", out _); + }, Throws.ArgumentNullException); + } + + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] + public void Verify_that_TryGetModelEntryByIndexKey_throws_when_indexKey_is_invalid(string indexKey) + { + var archive = new Archive + { + Metadata = new InterchangeProjectMetadata { Index = new Dictionary() }, + Models = Array.Empty() + }; + + Assert.That(() => + { + archive.TryGetModelEntryByIndexKey(indexKey, out _); + }, Throws.ArgumentException); + } + + [Test] + public void Verify_that_TryGetModelEntryByIndexKey_returns_false_when_metadata_index_is_missing() + { + var archive = new Archive + { + Metadata = new InterchangeProjectMetadata { Index = null }, + Models = Array.Empty() + }; + + var ok = archive.TryGetModelEntryByIndexKey("Base", out var entry); + + Assert.That(ok, Is.False); + Assert.That(entry, Is.Null); + } + + [Test] + public void Verify_that_TryGetModelEntryByIndexKey_returns_false_when_key_is_missing() + { + var archive = new Archive + { + Metadata = new InterchangeProjectMetadata + { + Index = new Dictionary(StringComparer.Ordinal) + { + ["Other"] = "Other.kerml" + } + }, + Models = new[] + { + CreateModelEntry("Other.kerml") + } + }; + + var ok = archive.TryGetModelEntryByIndexKey("Base", out var entry); + + Assert.That(ok, Is.False); + Assert.That(entry, Is.Null); + } + + [Test] + public void Verify_that_TryGetModelEntryByIndexKey_returns_false_when_path_is_null_or_whitespace() + { + var archive = new Archive + { + Metadata = new InterchangeProjectMetadata + { + Index = new Dictionary(StringComparer.Ordinal) + { + ["Base"] = " " + } + }, + Models = new[] + { + CreateModelEntry("Base.kerml") + } + }; + + var ok = archive.TryGetModelEntryByIndexKey("Base", out var entry); + + Assert.That(ok, Is.False); + Assert.That(entry, Is.Null); + } + + [Test] + public void Verify_that_TryGetModelEntryByIndexKey_returns_false_when_model_is_not_present() + { + var archive = new Archive + { + Metadata = new InterchangeProjectMetadata + { + Index = new Dictionary(StringComparer.Ordinal) + { + ["Base"] = "Base.kerml" + } + }, + Models = new[] + { + CreateModelEntry("Other.kerml") + } + }; + + var ok = archive.TryGetModelEntryByIndexKey("Base", out var entry); + + Assert.That(ok, Is.False); + Assert.That(entry, Is.Null); + } + + [Test] + public void Verify_that_TryGetModelEntryByIndexKey_returns_true_and_sets_entry_when_found() + { + var expected = CreateModelEntry("Base.kerml"); + + var archive = new Archive + { + Metadata = new InterchangeProjectMetadata + { + Index = new Dictionary(StringComparer.Ordinal) + { + ["Base"] = "Base.kerml" + } + }, + Models = new[] + { + expected, + CreateModelEntry("Other.kerml") + } + }; + + var ok = archive.TryGetModelEntryByIndexKey("Base", out var entry); + + Assert.That(ok, Is.True); + Assert.That(entry, Is.SameAs(expected)); + } + + [Test] + public void Verify_that_TryGetModelEntryByIndexKey_matches_using_normalized_paths() + { + // Index uses backslashes; model entry uses forward slashes. + var expected = CreateModelEntry("folder/Base.kerml"); + + var archive = new Archive + { + Metadata = new InterchangeProjectMetadata + { + Index = new Dictionary(StringComparer.Ordinal) + { + ["Base"] = @"folder\Base.kerml" + } + }, + Models = new[] + { + expected + } + }; + + var ok = archive.TryGetModelEntryByIndexKey("Base", out var entry); + + Assert.That(ok, Is.True); + Assert.That(entry, Is.SameAs(expected)); + } + + [Test] + public void Verify_that_GetModelEntryByIndexKey_throws_when_archive_is_null() + { + Assert.That(() => ArchiveExtensions.GetModelEntryByIndexKey(null, "Base"), Throws.ArgumentNullException); + } + + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] + public void Verify_that_GetModelEntryByIndexKey_throws_when_indexKey_is_invalid(string indexKey) + { + var archive = new Archive + { + Metadata = new InterchangeProjectMetadata { Index = new Dictionary() }, + Models = Array.Empty() + }; + + Assert.That(() => archive.GetModelEntryByIndexKey(indexKey), Throws.ArgumentException); + } + + [Test] + public void Verify_that_GetModelEntryByIndexKey_throws_when_metadata_index_is_not_available() + { + var archive = new Archive + { + Metadata = null, + Models = Array.Empty() + }; + + Assert.That(() => archive.GetModelEntryByIndexKey("Base"), Throws.InvalidOperationException); + } + + [Test] + public void Verify_that_GetModelEntryByIndexKey_throws_when_key_is_not_found() + { + var archive = new Archive + { + Metadata = new InterchangeProjectMetadata + { + Index = new Dictionary(StringComparer.Ordinal) + { + ["Other"] = "Other.kerml" + } + }, + Models = new[] { CreateModelEntry("Other.kerml") } + }; + + Assert.That(() => archive.GetModelEntryByIndexKey("Base"), Throws.TypeOf()); + } + + [Test] + public void Verify_that_GetModelEntryByIndexKey_throws_when_index_entry_path_is_null_or_whitespace() + { + var archive = new Archive + { + Metadata = new InterchangeProjectMetadata + { + Index = new Dictionary(StringComparer.Ordinal) + { + ["Base"] = " " + } + }, + Models = new[] { CreateModelEntry("Base.kerml") } + }; + + Assert.That(() => archive.GetModelEntryByIndexKey("Base"), Throws.TypeOf()); + } + + [Test] + public void Verify_that_GetModelEntryByIndexKey_throws_when_model_entry_is_missing() + { + var archive = new Archive + { + Metadata = new InterchangeProjectMetadata + { + Index = new Dictionary(StringComparer.Ordinal) + { + ["Base"] = "Base.kerml" + } + }, + Models = new[] { CreateModelEntry("Other.kerml") } + }; + + Assert.That(() => archive.GetModelEntryByIndexKey("Base"), Throws.TypeOf()); + } + + [Test] + public void Verify_that_GetModelEntryByIndexKey_returns_entry_when_found() + { + var expected = CreateModelEntry("Base.kerml"); + + var archive = new Archive + { + Metadata = new InterchangeProjectMetadata + { + Index = new Dictionary(StringComparer.Ordinal) + { + ["Base"] = "Base.kerml" + } + }, + Models = new[] { expected } + }; + + var entry = archive.GetModelEntryByIndexKey("Base"); + + Assert.That(entry, Is.SameAs(expected)); + } + + [Test] + public async Task Verify_that_OpenModelByIndexKeyAsync_opens_stream_from_entry() + { + var expectedBytes = Encoding.UTF8.GetBytes("hello kerml"); + + var entry = new ModelEntry + { + Path = "Base.kerml", + ContentType = "text/plain", + OpenReadAsync = _ => new ValueTask(new MemoryStream(expectedBytes, writable: false)) + }; + + var archive = new Archive + { + Metadata = new InterchangeProjectMetadata + { + Index = new Dictionary(StringComparer.Ordinal) + { + ["Base"] = "Base.kerml" + } + }, + Models = new[] { entry } + }; + + await using var stream = await archive.OpenModelByIndexKeyAsync("Base", CancellationToken.None); + + Assert.That(stream, Is.Not.Null); + using var ms = new MemoryStream(); + await stream.CopyToAsync(ms); + Assert.That(ms.ToArray(), Is.EqualTo(expectedBytes)); + } + + private static ModelEntry CreateModelEntry(string path) + { + return new ModelEntry + { + Path = path, + ContentType = "text/plain", + OpenReadAsync = _ => new ValueTask(new MemoryStream(Array.Empty(), writable: false)) + }; + } + } +} diff --git a/SysML2.NET.Extensions.Tests/ModelInterchange/ChecksumKindProviderTestFixture.cs b/SysML2.NET.Extensions.Tests/ModelInterchange/ChecksumKindProviderTestFixture.cs new file mode 100644 index 000000000..2e0e457dc --- /dev/null +++ b/SysML2.NET.Extensions.Tests/ModelInterchange/ChecksumKindProviderTestFixture.cs @@ -0,0 +1,232 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Extensions.Tests.Core.ModelInterchange +{ + using System; + using System.Buffers; + using System.Linq; + using System.Text; + + using NUnit.Framework; + + using SysML2.NET.ModelInterchange; + + [TestFixture] + public class ChecksumKindProviderTestFixture + { + [Test] + public void Verify_that_on_Parse_when_value_is_null_then_ArgumentNullException_is_thrown() + { + Assert.That(() => + { + ReadOnlySpan value = null; + ChecksumKindProvider.Parse(value); + }, Throws.ArgumentNullException); + + Assert.That(() => + { + ReadOnlySpan value = null; + ChecksumKindProvider.Parse(value); + }, Throws.ArgumentNullException); + } + + [TestCase(ChecksumKind.SHA1, "SHA1")] + [TestCase(ChecksumKind.SHA224, "SHA224")] + [TestCase(ChecksumKind.SHA256, "SHA256")] + [TestCase(ChecksumKind.SHA384, "SHA-384")] + [TestCase(ChecksumKind.SHA3256, "SHA3-256")] + [TestCase(ChecksumKind.SHA3384, "SHA3-384")] + [TestCase(ChecksumKind.SHA3512, "SHA3-512")] + [TestCase(ChecksumKind.BLAKE2b256, "BLAKE2b-256")] + [TestCase(ChecksumKind.BLAKE2b384, "BLAKE2b-384")] + [TestCase(ChecksumKind.BLAKE2b512, "BLAKE2b-512")] + [TestCase(ChecksumKind.BLAKE3, "BLAKE3")] + [TestCase(ChecksumKind.MD2, "MD2")] + [TestCase(ChecksumKind.MD4, "MD4")] + [TestCase(ChecksumKind.MD5, "MD5")] + [TestCase(ChecksumKind.MD6, "MD6")] + [TestCase(ChecksumKind.ADLER32, "ADLER32")] + public void Verify_that_Parse_charSpan_parses_known_values_case_insensitively(ChecksumKind expected, string token) + { + // Upper + Assert.That(ChecksumKindProvider.Parse(token.AsSpan()), Is.EqualTo(expected)); + + // Lower + Assert.That(ChecksumKindProvider.Parse(token.ToLowerInvariant().AsSpan()), Is.EqualTo(expected)); + + // Mixed (simple mixed variant) + var mixed = token.Length > 1 + ? char.ToLowerInvariant(token[0]) + token.Substring(1).ToUpperInvariant() + : token; + Assert.That(ChecksumKindProvider.Parse(mixed.AsSpan()), Is.EqualTo(expected)); + } + + [TestCase(ChecksumKind.SHA1, "SHA1")] + [TestCase(ChecksumKind.SHA224, "SHA224")] + [TestCase(ChecksumKind.SHA256, "SHA256")] + [TestCase(ChecksumKind.SHA384, "SHA-384")] + [TestCase(ChecksumKind.SHA3256, "SHA3-256")] + [TestCase(ChecksumKind.SHA3384, "SHA3-384")] + [TestCase(ChecksumKind.SHA3512, "SHA3-512")] + [TestCase(ChecksumKind.BLAKE2b256, "BLAKE2b-256")] + [TestCase(ChecksumKind.BLAKE2b384, "BLAKE2b-384")] + [TestCase(ChecksumKind.BLAKE2b512, "BLAKE2b-512")] + [TestCase(ChecksumKind.BLAKE3, "BLAKE3")] + [TestCase(ChecksumKind.MD2, "MD2")] + [TestCase(ChecksumKind.MD4, "MD4")] + [TestCase(ChecksumKind.MD5, "MD5")] + [TestCase(ChecksumKind.MD6, "MD6")] + [TestCase(ChecksumKind.ADLER32, "ADLER32")] + public void Verify_that_Parse_utf8Span_parses_known_values_case_sensitively(ChecksumKind expected, string token) + { + // Note: this overload uses SequenceEqual against uppercase literals. + var utf8 = Encoding.UTF8.GetBytes(token); + Assert.That(ChecksumKindProvider.Parse(utf8.AsSpan()), Is.EqualTo(expected)); + } + + [TestCase(ChecksumKind.SHA1, "SHA1")] + [TestCase(ChecksumKind.SHA224, "SHA224")] + [TestCase(ChecksumKind.SHA256, "SHA256")] + [TestCase(ChecksumKind.SHA384, "SHA-384")] + [TestCase(ChecksumKind.SHA3256, "SHA3-256")] + [TestCase(ChecksumKind.SHA3384, "SHA3-384")] + [TestCase(ChecksumKind.SHA3512, "SHA3-512")] + [TestCase(ChecksumKind.BLAKE2b256, "BLAKE2b-256")] + [TestCase(ChecksumKind.BLAKE2b384, "BLAKE2b-384")] + [TestCase(ChecksumKind.BLAKE2b512, "BLAKE2b-512")] + [TestCase(ChecksumKind.BLAKE3, "BLAKE3")] + [TestCase(ChecksumKind.MD2, "MD2")] + [TestCase(ChecksumKind.MD4, "MD4")] + [TestCase(ChecksumKind.MD5, "MD5")] + [TestCase(ChecksumKind.MD6, "MD6")] + [TestCase(ChecksumKind.ADLER32, "ADLER32")] + public void Verify_that_Parse_readonlySequence_parses_single_segment(ChecksumKind expected, string token) + { + var bytes = Encoding.UTF8.GetBytes(token); + var sequence = new ReadOnlySequence(bytes); + Assert.That(ChecksumKindProvider.Parse(sequence), Is.EqualTo(expected)); + } + + [Test] + public void Verify_that_Parse_readonlySequence_parses_multi_segment_using_stackalloc_copy() + { + // Multi-segment "SHA256" -> "SHA" + "256" + var seg1 = Encoding.UTF8.GetBytes("SHA"); + var seg2 = Encoding.UTF8.GetBytes("256"); + + var first = new BufferSegment(seg1); + var last = first.Append(seg2); + + var sequence = new ReadOnlySequence(first, 0, last, last.Memory.Length); + + Assert.That(sequence.IsSingleSegment, Is.False); + Assert.That(ChecksumKindProvider.Parse(sequence), Is.EqualTo(ChecksumKind.SHA256)); + } + + [Test] + public void Verify_that_Parse_readonlySequence_throws_when_length_exceeds_16() + { + var bytes = Enumerable.Repeat((byte)'A', 17).ToArray(); + var sequence = new ReadOnlySequence(bytes); + + var ex = Assert.Throws(() => ChecksumKindProvider.Parse(sequence)); + Assert.That(ex!.ParamName, Is.EqualTo("value")); + Assert.That(ex.Message, Does.Contain("'AAAAAAAAAAAAAAAAA' is not a valid ChecksumKind (Parameter 'value')")); + } + + [Test] + public void Verify_that_Parse_charSpan_throws_for_unknown_value() + { + var ex = Assert.Throws(() => ChecksumKindProvider.Parse("NOPE".AsSpan())); + Assert.That(ex!.ParamName, Is.EqualTo("value")); + Assert.That(ex.Message, Does.Contain("is not a valid ChecksumKind")); + } + + [Test] + public void Verify_that_Parse_utf8Span_throws_for_unknown_value_and_message_contains_decoded_value() + { + var bytes = Encoding.UTF8.GetBytes("NOPE"); + var ex = Assert.Throws(() => ChecksumKindProvider.Parse(bytes.AsSpan())); + Assert.That(ex!.ParamName, Is.EqualTo("value")); + Assert.That(ex.Message, Does.Contain("NOPE")); + Assert.That(ex.Message, Does.Contain("is not a valid ChecksumKind")); + } + + [TestCase(ChecksumKind.SHA1, "SHA1")] + [TestCase(ChecksumKind.SHA224, "SHA224")] + [TestCase(ChecksumKind.SHA256, "SHA256")] + [TestCase(ChecksumKind.SHA384, "SHA-384")] + [TestCase(ChecksumKind.SHA3256, "SHA3-256")] + [TestCase(ChecksumKind.SHA3384, "SHA3-384")] + [TestCase(ChecksumKind.SHA3512, "SHA3-512")] + [TestCase(ChecksumKind.BLAKE2b256, "BLAKE2b-256")] + [TestCase(ChecksumKind.BLAKE2b384, "BLAKE2b-384")] + [TestCase(ChecksumKind.BLAKE2b512, "BLAKE2b-512")] + [TestCase(ChecksumKind.BLAKE3, "BLAKE3")] + [TestCase(ChecksumKind.MD2, "MD2")] + [TestCase(ChecksumKind.MD4, "MD4")] + [TestCase(ChecksumKind.MD5, "MD5")] + [TestCase(ChecksumKind.MD6, "MD6")] + [TestCase(ChecksumKind.ADLER32, "ADLER32")] + public void Verify_that_ToUtf8LowerBytes_returns_expected_token_and_roundtrips_via_Parse_utf8Span( + ChecksumKind kind, + string expectedToken) + { + var bytes = ChecksumKindProvider.ToUtf8LowerBytes(kind); + + // Verify exact bytes content + Assert.That(Encoding.UTF8.GetString(bytes), Is.EqualTo(expectedToken)); + + // Round-trip via Parse(ReadOnlySpan) + Assert.That(ChecksumKindProvider.Parse(bytes), Is.EqualTo(kind)); + } + + [Test] + public void Verify_that_ToUtf8LowerBytes_throws_for_undefined_enum_value() + { + // Pick a clearly undefined value + var invalid = (ChecksumKind)123456; + Assert.Throws(() => ChecksumKindProvider.ToUtf8LowerBytes(invalid)); + } + + /// + /// Minimal ReadOnlySequence segment helper to build multi-segment sequences. + /// + private sealed class BufferSegment : ReadOnlySequenceSegment + { + public BufferSegment(ReadOnlyMemory memory) + { + Memory = memory; + } + + public BufferSegment Append(ReadOnlyMemory memory) + { + var segment = new BufferSegment(memory) + { + RunningIndex = RunningIndex + Memory.Length + }; + + Next = segment; + return segment; + } + } + } +} diff --git a/SysML2.NET.Extensions.Tests/Utilities/StreamExtensionsTestFixture.cs b/SysML2.NET.Extensions.Tests/Utilities/StreamExtensionsTestFixture.cs new file mode 100644 index 000000000..45d426e3c --- /dev/null +++ b/SysML2.NET.Extensions.Tests/Utilities/StreamExtensionsTestFixture.cs @@ -0,0 +1,221 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Tests.Extensions.Utilities +{ + using System; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + + using NUnit.Framework; + + using SysML2.NET.Extensions.Utilities; + + /// + /// Unit tests for . + /// + [TestFixture] + public sealed class StreamExtensionsTestFixture + { + [Test] + public void Verify_that_ReadAllBytes_throws_on_null_stream() + { + Stream stream = null; + + Assert.That(() => stream.ReadAllBytes(), + Throws.ArgumentNullException.With.Property(nameof(ArgumentNullException.ParamName)).EqualTo("stream")); + } + + [Test] + public async Task Verify_that_ReadAllBytesAsync_throws_on_null_stream() + { + Stream stream = null; + + var ex = Assert.ThrowsAsync(async () => + await stream.ReadAllBytesAsync(CancellationToken.None)); + + Assert.That(ex!.ParamName, Is.EqualTo("stream")); + } + + [Test] + public void Verify_that_ReadAllBytes_returns_empty_array_for_empty_stream() + { + using var ms = new MemoryStream(Array.Empty()); + + var bytes = ms.ReadAllBytes(); + + Assert.That(bytes, Is.Not.Null); + Assert.That(bytes, Is.Empty); + Assert.That(ms.Position, Is.EqualTo(ms.Length)); + } + + [Test] + public async Task Verify_that_ReadAllBytesAsync_returns_empty_array_for_empty_stream() + { + using var ms = new MemoryStream(Array.Empty()); + + var bytes = await ms.ReadAllBytesAsync(); + + Assert.That(bytes, Is.Not.Null); + Assert.That(bytes, Is.Empty); + Assert.That(ms.Position, Is.EqualTo(ms.Length)); + } + + [Test] + public void Verify_that_ReadAllBytes_reads_from_current_position_to_end() + { + var payload = CreateBytes(1024); + using var ms = new MemoryStream(payload); + + // advance stream + ms.Position = 100; + + var bytes = ms.ReadAllBytes(); + + Assert.That(bytes, Is.EqualTo(payload.AsSpan(100).ToArray())); + Assert.That(ms.Position, Is.EqualTo(ms.Length)); + } + + [Test] + public async Task Verify_that_ReadAllBytesAsync_reads_from_current_position_to_end() + { + var payload = CreateBytes(1024); + await using var ms = new MemoryStream(payload); + + ms.Position = 100; + + var bytes = await ms.ReadAllBytesAsync(); + + Assert.That(bytes, Is.EqualTo(payload.AsSpan(100).ToArray())); + Assert.That(ms.Position, Is.EqualTo(ms.Length)); + } + + [Test] + public void Verify_that_ReadAllBytes_handles_buffer_growth() + { + // Larger than initialSize (32 KiB) to force at least one growth. + var payload = CreateBytes(100 * 1024); + using var ms = new MemoryStream(payload); + + var bytes = ms.ReadAllBytes(); + + Assert.That(bytes, Is.EqualTo(payload)); + Assert.That(ms.Position, Is.EqualTo(ms.Length)); + } + + [Test] + public async Task Verify_that_ReadAllBytesAsync_handles_buffer_growth() + { + var payload = CreateBytes(100 * 1024); + await using var ms = new MemoryStream(payload); + + var bytes = await ms.ReadAllBytesAsync(); + + Assert.That(bytes, Is.EqualTo(payload)); + Assert.That(ms.Position, Is.EqualTo(ms.Length)); + } + + [Test] + public void Verify_that_ReadAllBytes_does_not_dispose_stream() + { + var payload = CreateBytes(1024); + var ms = new MemoryStream(payload); + + _ = ms.ReadAllBytes(); + + Assert.That(ms.CanRead, Is.True); + Assert.That(ms.CanSeek, Is.True); + + ms.Dispose(); + } + + [Test] + public async Task Verify_that_ReadAllBytesAsync_does_not_dispose_stream() + { + var payload = CreateBytes(1024); + var ms = new MemoryStream(payload); + + _ = await ms.ReadAllBytesAsync(); + + Assert.That(ms.CanRead, Is.True); + Assert.That(ms.CanSeek, Is.True); + + ms.Dispose(); + } + + [Test] + public void Verify_that_ReadAllBytesAsync_honors_cancellation() + { + // Use a stream that produces infinite data to ensure we hit cancellation deterministically. + using var stream = new InfiniteReadStream(); + + using var cts = new CancellationTokenSource(); + cts.Cancel(); + + Assert.That(async () => await stream.ReadAllBytesAsync(cts.Token), + Throws.InstanceOf()); + } + + private static byte[] CreateBytes(int length) + { + var bytes = new byte[length]; + for (var i = 0; i < bytes.Length; i++) + { + bytes[i] = (byte)(i & 0xFF); + } + + return bytes; + } + + /// + /// A stream that always returns data on reads (until canceled). + /// + private sealed class InfiniteReadStream : Stream + { + public override bool CanRead => true; + public override bool CanSeek => false; + public override bool CanWrite => false; + public override long Length => throw new NotSupportedException(); + public override long Position { get => 0; set => throw new NotSupportedException(); } + public override void Flush() => throw new NotSupportedException(); + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + + public override int Read(byte[] buffer, int offset, int count) + { + // Produce some bytes without ever reaching end-of-stream. + var n = Math.Min(count, 4096); + Array.Clear(buffer, offset, n); + return n; + } + + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var n = Math.Min(buffer.Length, 4096); + buffer.Span.Slice(0, n).Clear(); + return new ValueTask(n); + } + } + } +} diff --git a/SysML2.NET.Extensions.Tests/Utilities/StringExtensionsTestFixture.cs b/SysML2.NET.Extensions.Tests/Utilities/StringExtensionsTestFixture.cs new file mode 100644 index 000000000..5ca810009 --- /dev/null +++ b/SysML2.NET.Extensions.Tests/Utilities/StringExtensionsTestFixture.cs @@ -0,0 +1,120 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Extensions.Utilities.Tests +{ + using System; + + using NUnit.Framework; + + using SysML2.NET.Extensions.Utilities; + + [TestFixture] + public sealed class StringExtensionsTestFixture + { + [Test] + public void Verify_that_NormalizeZipPath_throws_when_path_is_null() + { + string path = null; + + Assert.That(() => path.NormalizeZipPath(), Throws.ArgumentNullException); + } + + [Test] + public void Verify_that_NormalizeZipPath_returns_empty_when_path_is_empty() + { + var result = string.Empty.NormalizeZipPath(); + + Assert.That(result, Is.EqualTo(string.Empty)); + } + + [Test] + public void Verify_that_NormalizeZipPath_replaces_backslashes_with_forward_slashes() + { + var input = @"a\b\c.txt"; + + var result = input.NormalizeZipPath(); + + Assert.That(result, Is.EqualTo("a/b/c.txt")); + } + + [Test] + public void Verify_that_NormalizeZipPath_removes_single_leading_dot_slash() + { + var input = "./Base.kerml"; + + var result = input.NormalizeZipPath(); + + Assert.That(result, Is.EqualTo("Base.kerml")); + } + + [Test] + public void Verify_that_NormalizeZipPath_removes_multiple_leading_dot_slash_segments() + { + var input = "./././folder/Base.kerml"; + + var result = input.NormalizeZipPath(); + + Assert.That(result, Is.EqualTo("folder/Base.kerml")); + } + + [Test] + public void Verify_that_NormalizeZipPath_removes_leading_dot_slash_after_backslash_replacement() + { + var input = @".\.\folder\Base.kerml"; + + var result = input.NormalizeZipPath(); + + Assert.That(result, Is.EqualTo("folder/Base.kerml")); + } + + [Test] + public void Verify_that_NormalizeZipPath_does_not_remove_dot_slash_in_middle_of_path() + { + var input = "folder/./Base.kerml"; + + var result = input.NormalizeZipPath(); + + Assert.That(result, Is.EqualTo("folder/./Base.kerml")); + } + + [Test] + public void Verify_that_NormalizeZipPath_does_not_trim_or_change_other_characters() + { + var input = " ./a/b "; + + var result = input.NormalizeZipPath(); + + // Only "./" prefix is removed; whitespace is preserved by design. + Assert.That(result, Is.EqualTo(" ./a/b ")); + } + + [Test] + public void Verify_that_NormalizeZipPath_is_idempotent() + { + var input = "./a\\b/c.kerml"; + + var once = input.NormalizeZipPath(); + var twice = once.NormalizeZipPath(); + + Assert.That(twice, Is.EqualTo(once)); + } + } +} diff --git a/SysML2.NET.Extensions/ModelInterchange/ArchiveExtensions.cs b/SysML2.NET.Extensions/ModelInterchange/ArchiveExtensions.cs new file mode 100644 index 000000000..e1d2f0e48 --- /dev/null +++ b/SysML2.NET.Extensions/ModelInterchange/ArchiveExtensions.cs @@ -0,0 +1,147 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Extensions.ModelInterchange +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + + using SysML2.NET.ModelInterchange; + using SysML2.NET.Extensions.Utilities; + + /// + /// Provides convenience methods for working with instances. + /// + public static class ArchiveExtensions + { + /// + /// Tries to resolve a by metadata index key. + /// + /// The to query. + /// + /// The key in that identifies a model file + /// (for example "Base" maps to "Base.kerml"). + /// + /// + /// When this method returns, contains the resolved if found; + /// otherwise, . + /// + /// + /// if an entry is resolved; otherwise, . + /// + public static bool TryGetModelEntryByIndexKey(this Archive archive, string indexKey, out ModelEntry entry) + { + if (archive == null) + { + throw new ArgumentNullException(nameof(archive)); + } + + if (string.IsNullOrWhiteSpace(indexKey)) + { + throw new ArgumentException("The index key shall not be null or empty.", nameof(indexKey)); + } + + entry = null; + + var map = archive.Metadata?.Index; + if (map is null) return false; + + if (!map.TryGetValue(indexKey, out var path) || string.IsNullOrWhiteSpace(path)) + { + return false; + } + + var normalized = path.NormalizeZipPath(); + + entry = archive.Models?.FirstOrDefault(modelEntry => + string.Equals(modelEntry.Path.NormalizeZipPath(), normalized, StringComparison.Ordinal)); + + return entry is not null; + } + + /// + /// Resolves a by metadata index key. + /// + /// The to query. + /// The metadata index key identifying the model file. + /// The resolved . + /// Thrown when is . + /// Thrown when is or empty. + /// + /// Thrown when the archive does not contain metadata index information. + /// + /// + /// Thrown when the metadata index does not contain . + /// + /// + /// Thrown when the metadata index points to a path that is not present in . + /// + public static ModelEntry GetModelEntryByIndexKey(this Archive archive, string indexKey) + { + if (archive is null) throw new ArgumentNullException(nameof(archive)); + + if (string.IsNullOrWhiteSpace(indexKey)) + { + throw new ArgumentException("The index key shall not be null or empty.", nameof(indexKey)); + } + + var map = archive.Metadata?.Index + ?? throw new InvalidOperationException("Archive metadata index is not available."); + + if (!map.TryGetValue(indexKey, out var path) || string.IsNullOrWhiteSpace(path)) + { + throw new KeyNotFoundException($"No index entry found for key '{indexKey}'."); + } + + var normalized = path.NormalizeZipPath(); + + var entry = archive.Models?.FirstOrDefault(m => + string.Equals(m.Path.NormalizeZipPath(), normalized, StringComparison.Ordinal)); + + if (entry is null) + { + throw new FileNotFoundException( + $"Model entry for index key '{indexKey}' points to missing path '{normalized}'.", + normalized); + } + + return entry; + } + + /// + /// Opens the model content stream associated with the specified metadata index key. + /// + /// The to query. + /// The metadata index key identifying the model file. + /// The cancellation token used to cancel the operation. + /// + /// A task that returns a readable stream for the model content. + /// + public static async Task OpenModelByIndexKeyAsync(this Archive archive, string indexKey, CancellationToken cancellationToken = default) + { + var entry = archive.GetModelEntryByIndexKey(indexKey); + return await entry.OpenReadAsync(cancellationToken); + } + } +} diff --git a/SysML2.NET.Extensions/ModelInterchange/ChecksumKindProvider.cs b/SysML2.NET.Extensions/ModelInterchange/ChecksumKindProvider.cs new file mode 100644 index 000000000..376cc8442 --- /dev/null +++ b/SysML2.NET.Extensions/ModelInterchange/ChecksumKindProvider.cs @@ -0,0 +1,264 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.ModelInterchange +{ + using System; + using System.Buffers; + + /// + /// Provides high-performance parsing and serialization helpers + /// for the enumeration. + /// + /// + /// + /// This provider enables zero-allocation parsing from + /// , , + /// and values. + /// + /// + /// It is optimized for streaming deserialization scenarios + /// such as System.Text.Json, MessagePack, and custom UTF-8 parsers. + /// + /// + /// All parsing operations use length-based short-circuit evaluation + /// to enable branch prediction and JIT-friendly control flow. + /// + /// + public static class ChecksumKindProvider + { + /// + /// Parses a textual checksum algorithm representation + /// into a enumeration value. + /// + /// + /// The character span representing the checksum algorithm. + /// + /// + /// The corresponding value. + /// + /// + /// Thrown when does not represent + /// a valid checksum algorithm. + /// + /// + /// + /// This overload is suited for string-based parsing. + /// It performs no heap allocations and avoids boxing. + /// + /// + /// Parsing is case-insensitive and uses + /// . + /// + /// + public static ChecksumKind Parse(ReadOnlySpan value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + switch (value.Length) + { + case 3: + if (value.Equals("MD2".AsSpan(), StringComparison.OrdinalIgnoreCase)) return ChecksumKind.MD2; + if (value.Equals("MD4".AsSpan(), StringComparison.OrdinalIgnoreCase)) return ChecksumKind.MD4; + if (value.Equals("MD5".AsSpan(), StringComparison.OrdinalIgnoreCase)) return ChecksumKind.MD5; + if (value.Equals("MD6".AsSpan(), StringComparison.OrdinalIgnoreCase)) return ChecksumKind.MD6; + break; + case 4: + if (value.Equals("SHA1".AsSpan(), StringComparison.OrdinalIgnoreCase)) return ChecksumKind.SHA1; + break; + case 6: + if (value.Equals("SHA224".AsSpan(), StringComparison.OrdinalIgnoreCase)) return ChecksumKind.SHA224; + if (value.Equals("SHA256".AsSpan(), StringComparison.OrdinalIgnoreCase)) return ChecksumKind.SHA256; + if (value.Equals("BLAKE3".AsSpan(), StringComparison.OrdinalIgnoreCase)) return ChecksumKind.BLAKE3; + break; + case 7: + if (value.Equals("ADLER32".AsSpan(), StringComparison.OrdinalIgnoreCase)) return ChecksumKind.ADLER32; + if (value.Equals("SHA-384".AsSpan(), StringComparison.OrdinalIgnoreCase)) return ChecksumKind.SHA384; + break; + case 8: + if (value.Equals("SHA3-256".AsSpan(), StringComparison.OrdinalIgnoreCase)) return ChecksumKind.SHA3256; + if (value.Equals("SHA3-384".AsSpan(), StringComparison.OrdinalIgnoreCase)) return ChecksumKind.SHA3384; + if (value.Equals("SHA3-512".AsSpan(), StringComparison.OrdinalIgnoreCase)) return ChecksumKind.SHA3512; + break; + case 11: + if (value.Equals("BLAKE2b-256".AsSpan(), StringComparison.OrdinalIgnoreCase)) return ChecksumKind.BLAKE2b256; + if (value.Equals("BLAKE2b-384".AsSpan(), StringComparison.OrdinalIgnoreCase)) return ChecksumKind.BLAKE2b384; + if (value.Equals("BLAKE2b-512".AsSpan(), StringComparison.OrdinalIgnoreCase)) return ChecksumKind.BLAKE2b512; + break; + } + + throw new ArgumentException($"'{new string(value)}' is not a valid ChecksumKind", nameof(value)); + } + + /// + /// Parses a UTF-8 encoded byte span into a + /// enumeration value. + /// + /// + /// The UTF-8 encoded byte span representing the checksum algorithm. + /// + /// + /// The corresponding value. + /// + /// + /// Thrown when does not represent + /// a valid checksum algorithm. + /// + public static ChecksumKind Parse(ReadOnlySpan value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + switch (value.Length) + { + case 3: + if (value.SequenceEqual("MD2"u8)) return ChecksumKind.MD2; + if (value.SequenceEqual("MD4"u8)) return ChecksumKind.MD4; + if (value.SequenceEqual("MD5"u8)) return ChecksumKind.MD5; + if (value.SequenceEqual("MD6"u8)) return ChecksumKind.MD6; + break; + case 4: + if (value.SequenceEqual("SHA1"u8)) return ChecksumKind.SHA1; + break; + case 6: + if (value.SequenceEqual("SHA224"u8)) return ChecksumKind.SHA224; + if (value.SequenceEqual("SHA256"u8)) return ChecksumKind.SHA256; + if (value.SequenceEqual("BLAKE3"u8)) return ChecksumKind.BLAKE3; + break; + case 7: + if (value.SequenceEqual("SHA-384"u8)) return ChecksumKind.SHA384; + if (value.SequenceEqual("ADLER32"u8)) return ChecksumKind.ADLER32; + break; + case 8: + if (value.SequenceEqual("SHA3-256"u8)) return ChecksumKind.SHA3256; + if (value.SequenceEqual("SHA3-384"u8)) return ChecksumKind.SHA3384; + if (value.SequenceEqual("SHA3-512"u8)) return ChecksumKind.SHA3512; + break; + case 11: + if (value.SequenceEqual("BLAKE2b-256"u8)) return ChecksumKind.BLAKE2b256; + if (value.SequenceEqual("BLAKE2b-384"u8)) return ChecksumKind.BLAKE2b384; + if (value.SequenceEqual("BLAKE2b-512"u8)) return ChecksumKind.BLAKE2b512; + break; + } + + throw new ArgumentException($"'{System.Text.Encoding.UTF8.GetString(value)}' is not a valid ChecksumKind", nameof(value)); + } + + /// + /// Parses a UTF-8 encoded into + /// a enumeration value. + /// + /// + /// A UTF-8 encoded sequence representing a checksum algorithm. + /// + /// + /// The corresponding value. + /// + /// + /// Thrown when the supplied sequence does not represent + /// a valid checksum algorithm. + /// + /// + /// + /// If the sequence is contiguous, parsing is delegated to the + /// overload. + /// + /// + /// For multi-segment sequences, the content is copied into + /// a stack-allocated buffer before parsing. + /// + /// + /// No heap allocations are performed. + /// + /// + public static ChecksumKind Parse(in ReadOnlySequence value) + { + if (value.IsSingleSegment) + { + return Parse(value.FirstSpan); + } + + if (value.Length > 16) + { + throw new ArgumentException("Invalid ChecksumKind length", nameof(value)); + } + + Span tmp = stackalloc byte[(int)value.Length]; + value.CopyTo(tmp); + return Parse(tmp); + } + + /// + /// Converts a value into its + /// UTF-8 encoded byte representation. + /// + /// + /// The to convert. + /// + /// + /// A UTF-8 encoded byte span representing the checksum algorithm. + /// + /// + /// Thrown when is not a defined + /// enumeration literal. + /// + /// + /// + /// This method is optimized for serialization scenarios, + /// such as MessagePack or JSON writers. + /// + /// + /// The returned span is backed by static UTF-8 data + /// and remains valid for the lifetime of the process. + /// + /// + /// No heap allocations, no boxing, branch-predictable switch. + /// + /// + public static ReadOnlySpan ToUtf8LowerBytes(ChecksumKind value) + { + return value switch + { + ChecksumKind.SHA1 => "SHA1"u8, + ChecksumKind.SHA224 => "SHA224"u8, + ChecksumKind.SHA256 => "SHA256"u8, + ChecksumKind.SHA384 => "SHA-384"u8, + ChecksumKind.SHA3256 => "SHA3-256"u8, + ChecksumKind.SHA3384 => "SHA3-384"u8, + ChecksumKind.SHA3512 => "SHA3-512"u8, + ChecksumKind.BLAKE2b256 => "BLAKE2b-256"u8, + ChecksumKind.BLAKE2b384 => "BLAKE2b-384"u8, + ChecksumKind.BLAKE2b512 => "BLAKE2b-512"u8, + ChecksumKind.BLAKE3 => "BLAKE3"u8, + ChecksumKind.MD2 => "MD2"u8, + ChecksumKind.MD4 => "MD4"u8, + ChecksumKind.MD5 => "MD5"u8, + ChecksumKind.MD6 => "MD6"u8, + ChecksumKind.ADLER32 => "ADLER32"u8, + _ => throw new ArgumentOutOfRangeException(nameof(value)) + }; + } + } +} diff --git a/SysML2.NET.Extensions/Utilities/StreamExtensions.cs b/SysML2.NET.Extensions/Utilities/StreamExtensions.cs new file mode 100644 index 000000000..896587af0 --- /dev/null +++ b/SysML2.NET.Extensions/Utilities/StreamExtensions.cs @@ -0,0 +1,152 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Extensions.Utilities +{ + using System; + using System.Buffers; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + + /// + /// Provides high-performance extension methods for reading + /// instances into byte arrays. + /// + /// + /// These methods: + /// + /// Read from the current stream position to end-of-stream. + /// Use pooled buffers to minimize intermediate allocations. + /// Return a newly allocated, right-sized byte array. + /// + /// The input stream is not disposed. + /// + public static class StreamExtensions + { + /// + /// Reads all remaining bytes from the stream into a new byte array. + /// + /// The readable stream. + /// + /// A byte array whose length exactly matches the number of bytes + /// read from the stream. + /// + /// + /// Thrown when is . + /// + public static byte[] ReadAllBytes(this Stream stream) + { + if (stream is null) throw new ArgumentNullException(nameof(stream)); + + const int initialSize = 32 * 1024; + + var rented = ArrayPool.Shared.Rent(initialSize); + + try + { + var total = 0; + + while (true) + { + var read = stream.Read(rented, total, rented.Length - total); + if (read == 0) break; + + total += read; + + if (total == rented.Length) + { + var newBuf = ArrayPool.Shared.Rent(rented.Length * 2); + Buffer.BlockCopy(rented, 0, newBuf, 0, total); + + ArrayPool.Shared.Return(rented, clearArray: true); + rented = newBuf; + } + } + + var result = new byte[total]; + Buffer.BlockCopy(rented, 0, result, 0, total); + + return result; + } + finally + { + ArrayPool.Shared.Return(rented, clearArray: true); + } + } + + /// + /// Asynchronously reads all remaining bytes from the stream into a new byte array. + /// + /// The readable stream. + /// A token used to cancel the operation. + /// + /// A task producing a byte array whose length exactly matches + /// the number of bytes read. + /// + /// + /// Thrown when is . + /// + public static async Task ReadAllBytesAsync(this Stream stream, CancellationToken cancellationToken = default) + { + if (stream is null) throw new ArgumentNullException(nameof(stream)); + + const int initialSize = 32 * 1024; + + var rented = ArrayPool.Shared.Rent(initialSize); + + try + { + var total = 0; + + while (true) + { + cancellationToken.ThrowIfCancellationRequested(); + + var read = await stream + .ReadAsync(rented.AsMemory(total, rented.Length - total), cancellationToken) + .ConfigureAwait(false); + + if (read == 0) break; + + total += read; + + if (total == rented.Length) + { + var newBuf = ArrayPool.Shared.Rent(rented.Length * 2); + Buffer.BlockCopy(rented, 0, newBuf, 0, total); + + ArrayPool.Shared.Return(rented, clearArray: true); + rented = newBuf; + } + } + + var result = new byte[total]; + Buffer.BlockCopy(rented, 0, result, 0, total); + + return result; + } + finally + { + ArrayPool.Shared.Return(rented, clearArray: true); + } + } + } +} diff --git a/SysML2.NET.Extensions/Utilities/StringExtensions.cs b/SysML2.NET.Extensions/Utilities/StringExtensions.cs new file mode 100644 index 000000000..ec207636d --- /dev/null +++ b/SysML2.NET.Extensions/Utilities/StringExtensions.cs @@ -0,0 +1,58 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Extensions.Utilities +{ + using System; + + /// + /// The class provides extensions methods for + /// + public static class StringExtensions + { + /// + /// Normalizes a ZIP entry path by converting directory separators to forward + /// slashes ('/') and removing any leading "./" segment. + /// + /// + /// The ZIP entry path to normalize. + /// + /// + /// A normalized path using forward slashes as directory separators and + /// without a leading "./" segment, if present. + /// + public static string NormalizeZipPath(this string path) + { + if (path == null) + { + throw new ArgumentNullException(nameof(path)); + } + + path = path.Replace('\\', '/'); + + while (path.StartsWith("./", StringComparison.Ordinal)) + { + path = path.Substring(2); + } + + return path; + } + } +} diff --git a/SysML2.NET.Kpar.Tests/ArchiveSessionTestFixture.cs b/SysML2.NET.Kpar.Tests/ArchiveSessionTestFixture.cs new file mode 100644 index 000000000..47819947d --- /dev/null +++ b/SysML2.NET.Kpar.Tests/ArchiveSessionTestFixture.cs @@ -0,0 +1,76 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Kpar.Tests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.IO.Compression; + + using NUnit.Framework; + + using SysML2.NET.ModelInterchange; + + [TestFixture] + public class ArchiveSessionTestFixture + { + private ArchiveSession archiveSession; + + private static string GetKparPath() + { + return Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData", "Kernel_Semantic_Library-1.0.0.kpar"); + } + + [SetUp] + public void SetUp() + { + var kparPath = GetKparPath(); + using var fileStream = File.OpenRead(kparPath); + + var zip = new ZipArchive(fileStream, ZipArchiveMode.Read, false); + var archive = new Archive(); + archive.Metadata = new InterchangeProjectMetadata(); + archive.Metadata.Index.Add("123","456"); + + this.archiveSession = new ArchiveSession(fileStream, zip, archive); + } + + [Test] + public void Verify_that_when_on_OpenModel_index_is_null_or_whitespace_exception_is_thrown() + { + Assert.That(() =>this.archiveSession.OpenModel(null), Throws.TypeOf()); + Assert.That(() =>this.archiveSession.OpenModel(""), Throws.TypeOf()); + } + + [Test] + public void Verify_that_when_on_OpenModel_index_is_does_not_exist_exception_is_thrown() + { + Assert.That(() =>this.archiveSession.OpenModel("starion"), Throws.TypeOf()); + } + + [Test] + public void Verify_that_when_on_OpenEntry_path_is_null_or_whitespace_exception_is_thrown() + { + Assert.That(() =>this.archiveSession.OpenEntry(null), Throws.TypeOf()); + Assert.That(() =>this.archiveSession.OpenEntry(""), Throws.TypeOf()); + } + } +} diff --git a/SysML2.NET.Kpar.Tests/Cryptography/Adler32TestFixture.cs b/SysML2.NET.Kpar.Tests/Cryptography/Adler32TestFixture.cs new file mode 100644 index 000000000..43c8607b3 --- /dev/null +++ b/SysML2.NET.Kpar.Tests/Cryptography/Adler32TestFixture.cs @@ -0,0 +1,103 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Kpar.Tests.Cryptography +{ + using System; + using System.IO; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + + using NUnit.Framework; + + using SysML2.NET.Kpar.Cryptography; + + [TestFixture] + public class Adler32TestFixture + { + [Test] + public void Verify_that_adler32_for_empty_input_is_00000001() + { + var checksum = Adler32.Compute(ReadOnlySpan.Empty); + + Assert.That(checksum, Is.EqualTo(0x00000001u)); + Assert.That(Adler32.ToHexString(checksum), Is.EqualTo("00000001")); + } + + [Test] + public void Verify_that_adler32_for_hello_is_known_value() + { + var data = Encoding.ASCII.GetBytes("Hello"); + + var checksum = Adler32.Compute(data); + + Assert.That(Adler32.ToHexString(checksum), Is.EqualTo("058c01f5")); + } + + [Test] + public void Verify_that_adler32_for_wikipedia_test_vector_is_known_value() + { + var data = Encoding.ASCII.GetBytes("Wikipedia"); + + var checksum = Adler32.Compute(data); + + Assert.That(checksum, Is.EqualTo(0x11E60398u)); + Assert.That(Adler32.ToHexString(checksum), Is.EqualTo("11e60398")); + } + + [Test] + public void Verify_that_stream_and_span_computations_match() + { + var data = Encoding.UTF8.GetBytes("The quick brown fox jumps over the lazy dog"); + var spanChecksum = Adler32.Compute(data); + + using var ms = new MemoryStream(data); + var streamChecksum = Adler32.Compute(ms); + + Assert.That(streamChecksum, Is.EqualTo(spanChecksum)); + } + + [Test] + public async Task Verify_that_stream_async_and_span_computations_match() + { + var cts = new CancellationTokenSource(); + + var data = Encoding.UTF8.GetBytes("The quick brown fox jumps over the lazy dog"); + var spanChecksum = Adler32.Compute(data); + + await using var ms = new MemoryStream(data); + var asyncChecksum = await Adler32.ComputeAsync(ms, cts.Token).ConfigureAwait(false); + + Assert.That(asyncChecksum, Is.EqualTo(spanChecksum)); + } + + [Test] + public void Verify_that_adler32_is_deterministic() + { + var data = Encoding.UTF8.GetBytes("Determinism matters."); + + var c1 = Adler32.Compute(data); + var c2 = Adler32.Compute(data); + + Assert.That(c2, Is.EqualTo(c1)); + } + } +} diff --git a/SysML2.NET.Kpar.Tests/Cryptography/ChecksumServiceTestFixture.cs b/SysML2.NET.Kpar.Tests/Cryptography/ChecksumServiceTestFixture.cs new file mode 100644 index 000000000..79e802307 --- /dev/null +++ b/SysML2.NET.Kpar.Tests/Cryptography/ChecksumServiceTestFixture.cs @@ -0,0 +1,258 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Kpar.Tests.Cryptography +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.IO.Compression; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + + using Microsoft.Extensions.Logging; + + using NUnit.Framework; + + using Serilog; + + using SysML2.NET.Kpar.Cryptography; + using SysML2.NET.ModelInterchange; + + [TestFixture] + public class ChecksumServiceTestFixture + { + private ChecksumService checksumService; + + private Reader reader; + + private ILoggerFactory loggerFactory; + + private static string GetKparPath() + { + return Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData", "Kernel_Semantic_Library-1.0.0.kpar"); + } + + [OneTimeSetUp] + public void OneTimeSetUp() + { + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .WriteTo.Console() + .CreateLogger(); + + this.loggerFactory = LoggerFactory.Create(builder => { builder.AddSerilog(); }); + } + + [SetUp] + public void SetUp() + { + this.checksumService = new ChecksumService(); + this.reader = new Reader(this.checksumService, this.loggerFactory.CreateLogger()); + this.checksumService = new ChecksumService(); + } + + [Test] + public void Verify_that_checksums_can_be_validated_for_known_kpar() + { + var kparPath = GetKparPath(); + Assert.That(File.Exists(kparPath), Is.True, $"KPAR test file not found: {kparPath}"); + + var archive = this.reader.Read(kparPath); + Assert.That(archive?.Metadata, Is.Not.Null); + + using var zip = ZipFile.OpenRead(kparPath); + + var mismatches = this.checksumService.Validate(zip, archive.Metadata); + + Assert.That(mismatches, Is.Not.Null); + Assert.That(mismatches.Count, Is.EqualTo(0)); + } + + [Test] + public async Task Verify_that_checksums_can_be_validated_async_for_known_kpar() + { + var kparPath = GetKparPath(); + Assert.That(File.Exists(kparPath), Is.True, $"KPAR test file not found: {kparPath}"); + + var archive = await this.reader.ReadAsync(kparPath).ConfigureAwait(false); + Assert.That(archive?.Metadata, Is.Not.Null); + + using var zip = ZipFile.OpenRead(kparPath); + + var mismatches = await this.checksumService.ValidateAsync(zip, archive.Metadata, cancellationToken: CancellationToken.None) + .ConfigureAwait(false); + + Assert.That(mismatches, Is.Not.Null); + Assert.That(mismatches.Count, Is.EqualTo(0)); + } + + [Test] + public void Verify_that_checksum_mismatch_throws_by_default() + { + var kparPath = GetKparPath(); + Assert.That(File.Exists(kparPath), Is.True, $"KPAR test file not found: {kparPath}"); + + var archive = this.reader.Read(kparPath); + var tampered = CloneMetadataWithSingleTamperedChecksumValue(archive.Metadata); + + using var zip = ZipFile.OpenRead(kparPath); + + var ex = Assert.Throws(() => this.checksumService.Validate(zip, tampered)); + Assert.That(ex, Is.Not.Null); + Assert.That(ex!.Mismatches, Is.Not.Null); + Assert.That(ex.Mismatches.Count, Is.EqualTo(1)); + + var m = ex.Mismatches[0]; + Assert.That(m.Path, Is.Not.Null.And.Not.Empty); + Assert.That(m.Expected, Is.Not.Null.And.Not.Empty); + Assert.That(m.Actual, Is.Not.Null.And.Not.Empty); + Assert.That(string.Equals(m.Expected, m.Actual, StringComparison.OrdinalIgnoreCase), Is.False); + } + + [Test] + public async Task Verify_that_checksum_mismatch_can_be_returned_when_behavior_is_return_mismatches() + { + var kparPath = GetKparPath(); + Assert.That(File.Exists(kparPath), Is.True, $"KPAR test file not found: {kparPath}"); + + var archive = await this.reader.ReadAsync(kparPath).ConfigureAwait(false); + var tampered = CloneMetadataWithSingleTamperedChecksumValue(archive.Metadata); + + using var zip = ZipFile.OpenRead(kparPath); + + var mismatches = await this.checksumService.ValidateAsync( + zip, + tampered, + behavior: ChecksumFailureBehavior.Collect, + cancellationToken: CancellationToken.None) + .ConfigureAwait(false); + + Assert.That(mismatches, Is.Not.Null); + Assert.That(mismatches.Count, Is.EqualTo(1)); + + var m = mismatches[0]; + Assert.That(m.Path, Is.Not.Null.And.Not.Empty); + Assert.That(m.Expected, Is.Not.Null.And.Not.Empty); + Assert.That(m.Actual, Is.Not.Null.And.Not.Empty); + Assert.That(string.Equals(m.Expected, m.Actual, StringComparison.OrdinalIgnoreCase), Is.False); + } + + [Test] + public void Verify_that_missing_zip_entry_throws_filenotfound() + { + var kparPath = GetKparPath(); + Assert.That(File.Exists(kparPath), Is.True, $"KPAR test file not found: {kparPath}"); + + var archive = this.reader.Read(kparPath); + var tampered = CloneMetadataWithSingleMissingPath(archive.Metadata); + + using var zip = ZipFile.OpenRead(kparPath); + + Assert.Throws(() => this.checksumService.Validate(zip, tampered)); + } + + private static InterchangeProjectMetadata CloneMetadataWithSingleTamperedChecksumValue(InterchangeProjectMetadata original) + { + ArgumentNullException.ThrowIfNull(original); + + if (original.Checksum is null || original.Checksum.Count == 0) + { + throw new InvalidOperationException("Test requires non-empty metadata.Checksum."); + } + + // Shallow clone is sufficient for the test; we only mutate one checksum value. + var clone = new InterchangeProjectMetadata + { + Created = original.Created, + Metamodel = original.Metamodel, + IncludesDerived = original.IncludesDerived, + IncludesImplied = original.IncludesImplied, + Index = original.Index is null ? null : new Dictionary(original.Index, StringComparer.Ordinal), + Checksum = new Dictionary(StringComparer.Ordinal) + }; + + foreach (var kvp in original.Checksum) + { + clone.Checksum[kvp.Key] = new InterchangeChecksum + { + Algorithm = kvp.Value.Algorithm, + Value = kvp.Value.Value + }; + } + + var firstKey = original.Checksum.Keys.First(); + var current = clone.Checksum[firstKey]; + + // Flip a nibble deterministically. + var value = current.Value?.Trim(); + if (string.IsNullOrWhiteSpace(value)) + { + throw new InvalidOperationException("Test requires non-empty checksum value."); + } + + var chars = value.ToCharArray(); + chars[0] = chars[0] == '0' ? '1' : '0'; + current.Value = new string(chars); + + return clone; + } + + private static InterchangeProjectMetadata CloneMetadataWithSingleMissingPath(InterchangeProjectMetadata original) + { + ArgumentNullException.ThrowIfNull(original); + + if (original.Checksum is null || original.Checksum.Count == 0) + { + throw new InvalidOperationException("Test requires non-empty metadata.Checksum."); + } + + var clone = new InterchangeProjectMetadata + { + Created = original.Created, + Metamodel = original.Metamodel, + IncludesDerived = original.IncludesDerived, + IncludesImplied = original.IncludesImplied, + Index = original.Index is null ? null : new Dictionary(original.Index, StringComparer.Ordinal), + Checksum = new Dictionary(StringComparer.Ordinal) + }; + + foreach (var kvp in original.Checksum) + { + clone.Checksum[kvp.Key] = new InterchangeChecksum + { + Algorithm = kvp.Value.Algorithm, + Value = kvp.Value.Value + }; + } + + var firstKey = original.Checksum.Keys.First(); + clone.Checksum.Remove(firstKey); + clone.Checksum["DoesNotExist.kerml"] = new InterchangeChecksum + { + Algorithm = ChecksumKind.SHA256, + Value = "00" + }; + + return clone; + } + } +} diff --git a/SysML2.NET.Kpar.Tests/ReaderTestFixture.cs b/SysML2.NET.Kpar.Tests/ReaderTestFixture.cs new file mode 100644 index 000000000..e740fbd33 --- /dev/null +++ b/SysML2.NET.Kpar.Tests/ReaderTestFixture.cs @@ -0,0 +1,350 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Kpar.Tests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using System.Threading.Tasks; + + using Microsoft.Extensions.Logging; + + using NUnit.Framework; + + using SysML2.NET.Kpar.Cryptography; + using SysML2.NET.ModelInterchange; + + using Serilog; + + [TestFixture] + public class ReaderTestFixture + { + private Reader reader; + + private ChecksumService checksumService; + + private ILoggerFactory loggerFactory; + + private static string GetKparPath() + { + return Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData", "Kernel_Semantic_Library-1.0.0.kpar"); + } + + [OneTimeSetUp] + public void OneTimeSetUp() + { + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .WriteTo.Console() + .CreateLogger(); + + this.loggerFactory = LoggerFactory.Create(builder => { builder.AddSerilog(); }); + } + + [SetUp] + public void SetUp() + { + this.checksumService = new ChecksumService(); + this.reader = new Reader(this.checksumService, this.loggerFactory.CreateLogger()); + } + + private static IEnumerable NullOrEmptyPaths() + { + yield return null; + yield return string.Empty; + } + + [Test] + [TestCaseSource(nameof(NullOrEmptyPaths))] + public void Verify_On_read_or_open_that_when_filepath_is_null_or_empty_argument_exception_is_thrown(string filePath) + { + Assert.That(() => this.reader.Read(filePath), Throws.ArgumentException); + + Assert.That(() => this.reader.Open(filePath), Throws.ArgumentException); + + Assert.That(async () => await this.reader.ReadAsync(filePath), Throws.ArgumentException); + + Assert.That(async () => await this.reader.OpenAsync(filePath), Throws.ArgumentException); + } + + [Test] + public void Verify_that_when_stream_is_null_ArgumentNullException_is_thrown() + { + Stream stream = null; + + Assert.That(() => this.reader.Read(stream), Throws.ArgumentNullException); + + Assert.That( async () => await this.reader.ReadAsync(stream), Throws.ArgumentNullException); + + Assert.That(() => this.reader.Open(stream), Throws.ArgumentNullException); + + Assert.That(async () => await this.reader.OpenAsync(stream), Throws.ArgumentNullException); + } + + [Test] + public void Verify_that_kpar_contents_can_be_read_from_path() + { + var kparPath = GetKparPath(); + + var archive = this.reader.Read(kparPath); + + AssertArchive(archive, expectedPath: kparPath); + } + + [Test] + public void Verify_that_kpar_contents_can_be_read_from_stream() + { + var kparPath = GetKparPath(); + + using var fileStream = File.OpenRead(kparPath); + + var archive = this.reader.Read(fileStream); + + AssertArchive(archive, expectedPath: null); + } + + [Test] + public async Task Verify_that_kpar_contents_can_be_read_async_from_path() + { + var kparPath = GetKparPath(); + + var archive = await this.reader.ReadAsync(kparPath).ConfigureAwait(false); + + AssertArchive(archive, expectedPath: kparPath); + } + + [Test] + public async Task Verify_that_kpar_contents_can_be_read_async_from_stream() + { + var kparPath = GetKparPath(); + + await using var fileStream = File.OpenRead(kparPath); + + var archive = await this.reader.ReadAsync(fileStream).ConfigureAwait(false); + + AssertArchive(archive, expectedPath: null); + } + + [Test] + public void Verify_that_kpar_can_be_opened_from_path_and_model_streams_can_be_opened() + { + var kparPath = GetKparPath(); + + using var archiveSession = this.reader.Open(kparPath); + + AssertArchive(archiveSession.Archive, expectedPath: kparPath); + + using var modelStream = archiveSession.OpenModel("Base"); + Assert.That(modelStream, Is.Not.Null); + Assert.That(modelStream.CanRead, Is.True); + + Assert.That(modelStream.Length, Is.GreaterThan(0)); + + using var reader = new StreamReader(modelStream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true); + + var content = reader.ReadToEnd(); + + TestContext.WriteLine("---- Base.kerml content ----"); + TestContext.WriteLine(content); + TestContext.WriteLine("---- End of content ----"); + } + + [Test] + public async Task Verify_that_kpar_can_be_opened_async_from_path_and_model_streams_can_be_opened() + { + var kparPath = GetKparPath(); + + await using var archiveSession = await this.reader.OpenAsync(kparPath).ConfigureAwait(false); + + AssertArchive(archiveSession.Archive, expectedPath: kparPath); + + await using var modelStream = archiveSession.OpenModel("Base"); + Assert.That(modelStream, Is.Not.Null); + Assert.That(modelStream.CanRead, Is.True); + Assert.That(modelStream.Length, Is.GreaterThan(0)); + + using var reader = new StreamReader(modelStream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true); + + var content = await reader.ReadToEndAsync(); + + TestContext.WriteLine("---- Base.kerml content ----"); + TestContext.WriteLine(content); + TestContext.WriteLine("---- End of content ----"); + } + + [Test] + public void Verify_that_opened_entry_streams_become_invalid_after_session_dispose() + { + var kparPath = GetKparPath(); + + var archiveSession = this.reader.Open(kparPath); + + var entryStream = archiveSession.OpenModel("Base"); + Assert.That(entryStream.CanRead, Is.True); + + archiveSession.Dispose(); + + Assert.That(() => _ = entryStream.ReadByte(), Throws.Exception, + "Reading from an entry stream after session disposal should fail."); + } + + [Test] + public async Task Verify_that_kpar_can_be_opened_async_from_stream_and_source_is_disposed_on_session_dispose() + { + var kparPath = GetKparPath(); + + var source = File.OpenRead(kparPath); + + ArchiveSession archiveSession = null; + + try + { + archiveSession = await this.reader.OpenAsync(source).ConfigureAwait(false); + + AssertArchive(archiveSession.Archive, expectedPath: null); + + await using var modelStream = archiveSession.OpenModel("Base"); + Assert.That(modelStream.CanRead, Is.True); + Assert.That(modelStream.Length, Is.GreaterThan(0)); + } + finally + { + if (archiveSession is not null) + { + await archiveSession.DisposeAsync().ConfigureAwait(false); + } + } + + Assert.That(source.CanRead, Is.False, "Source stream should be disposed ."); + } + + /// + /// Centralized assertions for all read paths (file/stream, sync/async). + /// + private static void AssertArchive(Archive archive, string expectedPath) + { + Assert.That(archive, Is.Not.Null); + + if (expectedPath is null) + { + Assert.That(archive.Path, Is.Null); + } + else + { + Assert.That(archive.Path, Is.EqualTo(expectedPath)); + } + + // ---- META.JSON ASSERTIONS ---- + Assert.That(archive.Metadata, Is.Not.Null); + + Assert.That(archive.Metadata.Created, Is.EqualTo(DateTimeOffset.Parse("2025-03-13T00:00:00Z"))); + Assert.That(archive.Metadata.Metamodel, Is.EqualTo("https://www.omg.org/spec/KerML/20250201")); + + Assert.That(archive.Metadata.IncludesDerived, Is.False); + Assert.That(archive.Metadata.IncludesImplied, Is.False); + + Assert.That(archive.Metadata.Index, Is.Not.Null); + Assert.That(archive.Metadata.Index.Count, Is.EqualTo(16)); + + Assert.Multiple(() => + { + Assert.That(archive.Metadata.Index["Base"], Is.EqualTo("Base.kerml")); + Assert.That(archive.Metadata.Index["Clocks"], Is.EqualTo("Clocks.kerml")); + Assert.That(archive.Metadata.Index["ControlPerformances"], Is.EqualTo("ControlPerformances.kerml")); + Assert.That(archive.Metadata.Index["FeatureReferencingPerformances"], Is.EqualTo("FeatureReferencingPerformances.kerml")); + Assert.That(archive.Metadata.Index["KerML"], Is.EqualTo("KerML.kerml")); + Assert.That(archive.Metadata.Index["Links"], Is.EqualTo("Links.kerml")); + Assert.That(archive.Metadata.Index["Metaobjects"], Is.EqualTo("Metaobjects.kerml")); + Assert.That(archive.Metadata.Index["Objects"], Is.EqualTo("Objects.kerml")); + Assert.That(archive.Metadata.Index["Observation"], Is.EqualTo("Observation.kerml")); + Assert.That(archive.Metadata.Index["Occurrences"], Is.EqualTo("Occurrences.kerml")); + Assert.That(archive.Metadata.Index["Performances"], Is.EqualTo("Performances.kerml")); + Assert.That(archive.Metadata.Index["SpatialFrames"], Is.EqualTo("SpatialFrames.kerml")); + Assert.That(archive.Metadata.Index["StatePerformances"], Is.EqualTo("StatePerformances.kerml")); + Assert.That(archive.Metadata.Index["Transfers"], Is.EqualTo("Transfers.kerml")); + Assert.That(archive.Metadata.Index["TransitionPerformances"], Is.EqualTo("TransitionPerformances.kerml")); + Assert.That(archive.Metadata.Index["Triggers"], Is.EqualTo("Triggers.kerml")); + }); + + Assert.That(archive.Metadata.Checksum, Is.Not.Null); + Assert.That(archive.Metadata.Checksum.Count, Is.EqualTo(16)); + + void AssertChecksum(string path, string expectedValue) + { + Assert.That(archive.Metadata.Checksum.ContainsKey(path), Is.True, $"Missing checksum for '{path}'"); + + var entry = archive.Metadata.Checksum[path]; + + Assert.That(entry, Is.Not.Null); + Assert.That(entry.Algorithm, Is.EqualTo(ChecksumKind.SHA256)); + Assert.That(entry.Value, Is.EqualTo(expectedValue)); + } + + Assert.Multiple(() => + { + AssertChecksum("Triggers.kerml", "124cad3625935e078d1363e6100ee12537ca9c51445a18108e056db8b4885609"); + AssertChecksum("ControlPerformances.kerml", "31385be7dca94bd0538f011d5c8f7925626d54f96970769f0fdb28b2186a9a03"); + AssertChecksum("Transfers.kerml", "fa40b483a7834d89f07aad0f6f57e79244adc2a58b4396c4734bddeb297d7c46"); + AssertChecksum("Objects.kerml", "9057e2781fe8793d5108973c0647318caa26310be6231c6380152a4cbc894c25"); + AssertChecksum("Metaobjects.kerml", "983dbd85a4b183d8859326ee512fc59d991fb98a115e009e72fad21d1f9d1685"); + AssertChecksum("Performances.kerml", "fd965e184b300737a192530de0c800cdbee236cb6220612f370400da21dfb327"); + AssertChecksum("StatePerformances.kerml", "f02fb7e8de58f4304c95c575ee1bcb7d271d621ce8e336ce36ea80a4e956c3da"); + AssertChecksum("Base.kerml", "56df84cda67f62c63d4e79e2786fc26046cfa361a958c4fcf0843d32a5707e09"); + AssertChecksum("Observation.kerml", "6bc57a73c43af6f61201b6eb659024a9f08f974643eb5a101e068e3637761ee4"); + AssertChecksum("TransitionPerformances.kerml", "1ce78437c817c8359a2cad43e8e72b23dd32b81d2a69dc1126c803fae72aae70"); + AssertChecksum("FeatureReferencingPerformances.kerml", "b6f9e5349c7c7f393591c0334c3bec86f1766b3e37209819179310c2f8fe1fb7"); + AssertChecksum("KerML.kerml", "8fdf4b7416e981c895cd74b75dc14b18091d13cbcaff7cdded6f9c23e2483d58"); + AssertChecksum("Occurrences.kerml", "b3a62ce0bc3a4f7e667102b4c2f68a4928ca8efeda425c6a4c8bdeadfbc9bbc1"); + AssertChecksum("Clocks.kerml", "960ac0884935e308beea55c78ed11b6946c37a386eb7958ef2c913aa275ae4c7"); + AssertChecksum("Links.kerml", "dcf3c002717cb91f8e16f1890fdf5526f4e178ada898a189621c7d0c24b5ddc0"); + AssertChecksum("SpatialFrames.kerml", "2a7790ebc2afacbd64eb781567906921e38eff385c917be03b090b8289353de7"); + }); + + foreach (var (_, path) in archive.Metadata.Index) + { + Assert.That( + archive.Metadata.Checksum.ContainsKey(path), + Is.True, + $"Index path '{path}' should have a checksum entry"); + } + + // ---- PROJECT.JSON ASSERTIONS ---- + Assert.That(archive.Project, Is.Not.Null); + + Assert.That(archive.Project.Name, Is.EqualTo("Kernel Semantic Library")); + Assert.That(archive.Project.Description, Is.EqualTo("Standard semantic library for the Kernel Modeling Language (KerML)")); + Assert.That(archive.Project.Version, Is.EqualTo("1.0.0")); + + Assert.That(archive.Project.Usage, Is.Not.Null); + Assert.That(archive.Project.Usage.Count, Is.EqualTo(2)); + + Assert.Multiple(() => + { + Assert.That(archive.Project.Usage[0].Resource, Is.EqualTo(new Uri("https://www.omg.org/spec/KerML/20250201/Data-Type-Library.kpar"))); + Assert.That(archive.Project.Usage[0].VersionConstraint, Is.EqualTo("1.0.0")); + + Assert.That(archive.Project.Usage[1].Resource, Is.EqualTo(new Uri("https://www.omg.org/spec/KerML/20250201/Function-Library.kpar"))); + Assert.That(archive.Project.Usage[1].VersionConstraint, Is.EqualTo("1.0.0")); + }); + } + } +} diff --git a/SysML2.NET.Kpar.Tests/SysML2.NET.Kpar.Tests.csproj b/SysML2.NET.Kpar.Tests/SysML2.NET.Kpar.Tests.csproj new file mode 100644 index 000000000..c4afd5cf5 --- /dev/null +++ b/SysML2.NET.Kpar.Tests/SysML2.NET.Kpar.Tests.csproj @@ -0,0 +1,77 @@ + + + + net9.0 + 12.0 + Starion Group S.A. + Sam Gerene + Nunit test suite for the SysML2.NET.Kpar library + Copyright © Starion Group S.A. + Apache-2.0 + https://github.com/STARIONGROUP/SysML2.NET.git + Git + false + disable + false + true + en-US + + + + + + + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + diff --git a/SysML2.NET.Kpar.Tests/WriterTestFixture.cs b/SysML2.NET.Kpar.Tests/WriterTestFixture.cs new file mode 100644 index 000000000..902646720 --- /dev/null +++ b/SysML2.NET.Kpar.Tests/WriterTestFixture.cs @@ -0,0 +1,34 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2025 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Kpar.Tests +{ + using NUnit.Framework; + + [TestFixture] + public class WriterTestFixture + { + [Test] + public void Inconclusive() + { + Assert.Inconclusive("write some tests when writer is implemented"); + } + } +} diff --git a/SysML2.NET.Kpar/ArchiveSession.cs b/SysML2.NET.Kpar/ArchiveSession.cs new file mode 100644 index 000000000..ca7e66594 --- /dev/null +++ b/SysML2.NET.Kpar/ArchiveSession.cs @@ -0,0 +1,144 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Kpar +{ + using System; + using System.IO; + using System.IO.Compression; + using System.Threading.Tasks; + using Extensions.ModelInterchange; + using SysML2.NET.Extensions.Utilities; + using SysML2.NET.ModelInterchange; + + /// + /// Represents an open session on a .kpar archive that keeps the underlying ZIP container + /// and source stream alive so that model content streams can be opened on demand. + /// + /// + /// The caller must dispose the session to release the underlying and + /// its backing . Any streams opened from the session become invalid once the + /// session is disposed. + /// + public sealed class ArchiveSession : IDisposable, IAsyncDisposable + { + /// + /// The underlying source stream that contains the .kpar ZIP archive. + /// + /// + /// This stream is kept alive for the lifetime of the + /// to allow on-demand access to ZIP entries. It is disposed when the session + /// is disposed, unless ownership is externally controlled. + /// + private readonly Stream source; + + /// + /// The open representing the .kpar container. + /// + /// + /// The ZIP archive remains open for the lifetime of the session to enable + /// deferred opening of model entry streams. Disposing the session disposes + /// this archive instance. + /// + private readonly ZipArchive zip; + + /// + /// Initializes a new instance of the class. + /// + /// The backing stream that contains the .kpar ZIP payload. + /// The open used to access entries. + /// The parsed logical archive representation. + internal ArchiveSession(Stream source, ZipArchive zip, Archive archive) + { + this.source = source ?? throw new ArgumentNullException(nameof(source)); + this.zip = zip ?? throw new ArgumentNullException(nameof(zip)); + this.Archive = archive ?? throw new ArgumentNullException(nameof(archive)); + } + + /// + /// Gets the logical archive contents parsed from the .kpar descriptors. + /// + public Archive Archive { get; } + + /// + /// Opens a model entry stream by metadata index key (for example "Base"). + /// + /// The key in .meta.json index. + /// A readable stream for the model file mapped by . + /// Thrown when is null or empty. + /// Thrown when metadata index is not available. + /// Thrown when is not present. + /// Thrown when the mapped path does not exist in the archive. + public Stream OpenModel(string indexKey) + { + var modelEntry = this.Archive.GetModelEntryByIndexKey(indexKey); + + return this.OpenEntry(modelEntry.Path); + } + + /// + /// Opens an entry stream by archive-relative path. + /// + /// + /// The archive-relative path of the entry to open (forward slashes preferred). + /// + /// + /// A readable stream for the requested entry. + /// + /// + /// Thrown when is null or empty. + /// + /// + /// Thrown when the specified entry does not exist in the archive. + /// + internal Stream OpenEntry(string archivePath) + { + if (string.IsNullOrWhiteSpace(archivePath)) + { + throw new ArgumentException("Archive path shall not be null or empty.", nameof(archivePath)); + } + + var normalized = archivePath.NormalizeZipPath(); + + var entry = this.zip.GetEntry(normalized); + + if (entry is null) + { + throw new FileNotFoundException($"kpar entry '{normalized}' not found.", normalized); + } + + return entry.Open(); + } + + /// + public void Dispose() + { + this.zip.Dispose(); + this.source.Dispose(); + } + + /// + public ValueTask DisposeAsync() + { + this.zip.Dispose(); + return this.source.DisposeAsync(); + } + } +} diff --git a/SysML2.NET.Kpar/ChecksumFailureBehavior.cs b/SysML2.NET.Kpar/ChecksumFailureBehavior.cs new file mode 100644 index 000000000..88c0c3769 --- /dev/null +++ b/SysML2.NET.Kpar/ChecksumFailureBehavior.cs @@ -0,0 +1,38 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Kpar +{ + /// + /// Defines how checksum mismatches are handled when checksum validation is enabled. + /// + public enum ChecksumFailureBehavior + { + /// + /// Throws a if one or more mismatches are detected. + /// + Throw = 0, + + /// + /// Collects mismatches and exposes them to the caller without throwing. + /// + Collect = 1 + } +} diff --git a/SysML2.NET.Kpar/ChecksumValidationException.cs b/SysML2.NET.Kpar/ChecksumValidationException.cs new file mode 100644 index 000000000..3b3db1617 --- /dev/null +++ b/SysML2.NET.Kpar/ChecksumValidationException.cs @@ -0,0 +1,54 @@ +namespace SysML2.NET.Kpar +{ + using System; + using System.Collections.Generic; + + using SysML2.NET.ModelInterchange; + + /// + /// Represents an exception that is thrown when checksum validation of a + /// .kpar archive fails. + /// + /// + /// This exception is raised when checksum validation is enabled via + /// and one or more model + /// files contained in the archive do not match the checksum values + /// declared in .meta.json. + /// + /// The default behavior (when is configured + /// to throw) is to abort archive opening and throw this exception + /// immediately after validation. + /// + /// The exception exposes detailed mismatch information through + /// the property. + /// + public sealed class ChecksumValidationException : InvalidOperationException + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The collection of checksum mismatches detected during validation. + /// + /// + /// The exception message summarizes the number of failed archive entries. + /// Detailed information about each mismatch is available via + /// the property. + /// + public ChecksumValidationException(IReadOnlyList mismatches) + : base($"Checksum validation failed for {mismatches?.Count ?? 0} archive entr{(mismatches?.Count == 1 ? "y" : "ies")}.") + { + this.Mismatches = mismatches ?? Array.Empty(); + } + + /// + /// Gets the collection of checksum mismatches detected during archive validation. + /// + /// + /// A read-only list of instances describing + /// each model entry whose computed checksum differed from the value declared + /// in .meta.json. + /// + public IReadOnlyList Mismatches { get; } + } +} diff --git a/SysML2.NET.Kpar/Cryptography/Adler32.cs b/SysML2.NET.Kpar/Cryptography/Adler32.cs new file mode 100644 index 000000000..61d4c4e27 --- /dev/null +++ b/SysML2.NET.Kpar/Cryptography/Adler32.cs @@ -0,0 +1,127 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Kpar.Cryptography +{ + using System; + using System.IO; + using System.Threading; + + /// + /// Provides an implementation of the Adler-32 checksum algorithm as defined in RFC 1950. + /// + /// + /// Adler-32 is a non-cryptographic checksum algorithm primarily used in zlib. + /// It is fast but not collision-resistant and MUST NOT be used for security-sensitive integrity validation. + /// + public static class Adler32 + { + private const uint ModAdler = 65521; + + /// + /// Computes the Adler-32 checksum for the specified byte span. + /// + /// The input data. + /// The computed Adler-32 checksum. + public static uint Compute(ReadOnlySpan data) + { + uint a = 1; + uint b = 0; + + foreach (var t in data) + { + a = (a + t) % ModAdler; + b = (b + a) % ModAdler; + } + + return (b << 16) | a; + } + + /// + /// Computes the Adler-32 checksum for the specified stream. + /// + /// The input stream. Must be readable. + /// The computed Adler-32 checksum. + /// Thrown if is null. + public static uint Compute(Stream stream) + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + + uint a = 1; + uint b = 0; + + Span buffer = stackalloc byte[8192]; + + int read; + while ((read = stream.Read(buffer)) > 0) + { + for (int i = 0; i < read; i++) + { + a = (a + buffer[i]) % ModAdler; + b = (b + a) % ModAdler; + } + } + + return (b << 16) | a; + } + + /// + /// Computes the Adler-32 checksum for the specified stream asynchronously. + /// + /// The input stream. Must be readable. + /// A token used to cancel the operation. + /// The computed Adler-32 checksum. + /// Thrown if is null. + public static async System.Threading.Tasks.Task ComputeAsync(Stream stream, CancellationToken cancellationToken ) + { + if (stream == null) throw new ArgumentNullException(nameof(stream)); + + uint a = 1; + uint b = 0; + + byte[] buffer = new byte[8192]; + + int read; + while ((read = await stream.ReadAsync(buffer).ConfigureAwait(false)) > 0) + { + cancellationToken.ThrowIfCancellationRequested(); + + for (int i = 0; i < read; i++) + { + a = (a + buffer[i]) % ModAdler; + b = (b + a) % ModAdler; + } + } + + return (b << 16) | a; + } + + /// + /// Converts the checksum value to a lowercase hexadecimal string (8 characters). + /// + /// The checksum value. + /// Lowercase hexadecimal string representation. + public static string ToHexString(uint checksum) + { + return checksum.ToString("x8"); + } + } +} diff --git a/SysML2.NET.Kpar/Cryptography/ChecksumService.cs b/SysML2.NET.Kpar/Cryptography/ChecksumService.cs new file mode 100644 index 000000000..162003d81 --- /dev/null +++ b/SysML2.NET.Kpar/Cryptography/ChecksumService.cs @@ -0,0 +1,408 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Kpar.Cryptography +{ + using System; + using System.Buffers; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.IO.Compression; + using System.Threading; + using System.Threading.Tasks; + + using Org.BouncyCastle.Crypto; + using Org.BouncyCastle.Crypto.Digests; + + using SysML2.NET.Extensions.Utilities; + using SysML2.NET.Kpar; + using SysML2.NET.ModelInterchange; + + /// + /// Provides checksum computation and validation utilities for .kpar archives. + /// + /// + /// This service: + /// + /// Computes checksums for supported values. + /// Validates the map against the actual ZIP entry contents. + /// Uses BouncyCastle for most digest algorithms and a built-in implementation for . + /// + /// + /// Algorithms that are not available in the current dependency set will raise . + /// + /// + public sealed class ChecksumService : IChecksumService + { + /// + /// Validates all checksum entries in against the corresponding ZIP entries. + /// + /// The open ZIP archive containing the .kpar entries. + /// The parsed .meta.json metadata. + /// + /// Controls what happens when mismatches are detected. The default is . + /// + /// + /// A read-only list of detected mismatches. If is , + /// this method throws instead of returning a non-empty list. + /// + public IReadOnlyList Validate(ZipArchive zip, InterchangeProjectMetadata metadata, ChecksumFailureBehavior behavior = ChecksumFailureBehavior.Throw) + { + if (zip is null) throw new ArgumentNullException(nameof(zip)); + + if (metadata is null) throw new ArgumentNullException(nameof(metadata)); + + var checksums = metadata.Checksum; + if (checksums is null || checksums.Count == 0) + { + return Array.Empty(); + } + + var mismatches = new List(); + + foreach (var kvp in checksums) + { + var archivePath = kvp.Key?.NormalizeZipPath(); + var expected = kvp.Value; + + if (string.IsNullOrWhiteSpace(archivePath) || expected is null) + { + continue; + } + + var entry = zip.GetEntry(archivePath); + if (entry is null) + { + throw new FileNotFoundException($"ZIP entry '{archivePath}' not found for checksum validation.", + archivePath); + } + + using var stream = entry.Open(); + var actualHex = ComputeHex(stream, expected.Algorithm); + + if (!HexEquals(actualHex, expected.Value)) + { + mismatches.Add(new ChecksumMismatch + { + IndexKey = kvp.Key, + Path = archivePath, + Algorithm = expected.Algorithm, + Expected =expected.Value, + Actual =actualHex + }); + } + } + + if (mismatches.Count > 0 && behavior == ChecksumFailureBehavior.Throw) + { + throw new ChecksumValidationException(mismatches); + } + + return mismatches; + } + + /// + /// Asynchronously validates all checksum entries in against the corresponding ZIP entries. + /// + /// The open ZIP archive containing the .kpar entries. + /// The parsed .meta.json metadata. + /// + /// Controls what happens when mismatches are detected. The default is . + /// + /// A token used to cancel the operation. + /// + /// A task that returns a read-only list of detected mismatches. If is , + /// this method throws instead of returning a non-empty list. + /// + public async Task> ValidateAsync(ZipArchive zip, InterchangeProjectMetadata metadata, ChecksumFailureBehavior behavior = ChecksumFailureBehavior.Throw, CancellationToken cancellationToken = default) + { + if (zip is null) throw new ArgumentNullException(nameof(zip)); + + if (metadata is null) throw new ArgumentNullException(nameof(metadata)); + + var checksums = metadata.Checksum; + if (checksums is null || checksums.Count == 0) + { + return Array.Empty(); + } + + var mismatches = new List(); + + foreach (var kvp in checksums) + { + cancellationToken.ThrowIfCancellationRequested(); + + var archivePath = kvp.Key?.NormalizeZipPath(); + var expected = kvp.Value; + + if (string.IsNullOrWhiteSpace(archivePath) || expected is null) + { + continue; + } + + var entry = zip.GetEntry(archivePath); + if (entry is null) + { + throw new FileNotFoundException($"ZIP entry '{archivePath}' not found for checksum validation.", + archivePath); + } + + await using var stream = entry.Open(); + var actualHex = await ComputeHexAsync(stream, expected.Algorithm, cancellationToken) + .ConfigureAwait(false); + + if (!HexEquals(actualHex, expected.Value)) + { + mismatches.Add(new ChecksumMismatch + { + IndexKey = kvp.Key, + Path = archivePath, + Algorithm = expected.Algorithm, + Expected =expected.Value, + Actual =actualHex + }); + } + } + + if (mismatches.Count > 0 && behavior == ChecksumFailureBehavior.Throw) + { + throw new ChecksumValidationException(mismatches); + } + + return mismatches; + } + + /// + /// Computes the checksum over the provided and returns a lowercase hex string. + /// + /// The input stream to hash. + /// The checksum algorithm to apply. + /// A lowercase hex string representing the computed checksum. + private static string ComputeHex(Stream stream, ChecksumKind kind) + { + if (stream is null) throw new ArgumentNullException(nameof(stream)); + + if (kind == ChecksumKind.ADLER32) + { + var adler = Adler32.Compute(stream); + return adler.ToString("x8", CultureInfo.InvariantCulture); + } + + var digest = CreateDigest(kind); + ComputeDigest(stream, digest); + var output = new byte[digest.GetDigestSize()]; + digest.DoFinal(output, 0); + return ToLowerHex(output); + } + + /// + /// Asynchronously computes the checksum over the provided and returns a lowercase hex string. + /// + /// The input stream to hash. + /// The checksum algorithm to apply. + /// A token used to cancel the operation. + /// A task returning a lowercase hex string representing the computed checksum. + private static async Task ComputeHexAsync(Stream stream, ChecksumKind kind, CancellationToken cancellationToken = default) + { + if (stream is null) throw new ArgumentNullException(nameof(stream)); + + if (kind == ChecksumKind.ADLER32) + { + var adler = await Adler32.ComputeAsync(stream, cancellationToken).ConfigureAwait(false); + return adler.ToString("x8", CultureInfo.InvariantCulture); + } + + var digest = CreateDigest(kind); + await ComputeDigestAsync(stream, digest, cancellationToken).ConfigureAwait(false); + var output = new byte[digest.GetDigestSize()]; + digest.DoFinal(output, 0); + return ToLowerHex(output); + } + + /// + /// Creates a BouncyCastle instance for the specified . + /// + /// The checksum algorithm to instantiate. + /// + /// A concrete implementation that can be fed with input bytes via + /// . + /// + /// + /// + /// This factory covers digest algorithms that are available in the referenced BouncyCastle package, + /// and intentionally throws for algorithms that require an additional dependency (for example BLAKE3, MD6). + /// + /// + /// Note that is not a cryptographic digest and is typically handled + /// outside BouncyCastle (for example with a dedicated Adler-32 implementation). + /// + /// + private static IDigest CreateDigest(ChecksumKind kind) + { + return kind switch + { + ChecksumKind.SHA1 => new Sha1Digest(), + ChecksumKind.SHA224 => new Sha224Digest(), + ChecksumKind.SHA256 => new Sha256Digest(), + ChecksumKind.SHA384 => new Sha384Digest(), + + ChecksumKind.SHA3256 => new Sha3Digest(256), + ChecksumKind.SHA3384 => new Sha3Digest(384), + ChecksumKind.SHA3512 => new Sha3Digest(512), + + // Blake2bDigest takes output size in bits. + ChecksumKind.BLAKE2b256 => new Blake2bDigest(256), + ChecksumKind.BLAKE2b384 => new Blake2bDigest(384), + ChecksumKind.BLAKE2b512 => new Blake2bDigest(512), + + ChecksumKind.MD2 => new MD2Digest(), + ChecksumKind.MD4 => new MD4Digest(), + ChecksumKind.MD5 => new MD5Digest(), + + // Not available in BouncyCastle by default (and not provided here). + ChecksumKind.BLAKE3 => throw new NotSupportedException( + "BLAKE3 is not supported by the current implementation. Add a BLAKE3 implementation and extend CreateDigest/ComputeHex accordingly."), + ChecksumKind.MD6 => throw new NotSupportedException( + "MD6 is not supported by the current implementation. Add an MD6 implementation and extend CreateDigest/ComputeHex accordingly."), + + _ => throw new NotSupportedException($"Checksum algorithm '{kind}' is not supported.") + }; + } + + /// + /// Reads all bytes from and feeds them into the provided . + /// + /// The readable input stream to hash from its current position to end-of-stream. + /// The digest instance that will receive the input bytes. + /// + /// Thrown when or is . + /// + /// + /// + /// This method does not reset the stream position. After completion, the stream is positioned at end-of-stream. + /// + /// + /// A pooled buffer is used to reduce allocations. The buffer is returned to the pool even if an exception occurs. + /// + /// + private static void ComputeDigest(Stream stream, IDigest digest) + { + var buffer = ArrayPool.Shared.Rent(64 * 1024); + try + { + int read; + while ((read = stream.Read(buffer, 0, buffer.Length)) > 0) + { + digest.BlockUpdate(buffer, 0, read); + } + } + finally + { + ArrayPool.Shared.Return(buffer); + } + } + + /// + /// Asynchronously reads all bytes from and feeds them into the provided . + /// + /// The readable input stream to hash from its current position to end-of-stream. + /// The digest instance that will receive the input bytes. + /// A token used to cancel the operation. + /// A task that completes when all bytes have been read and fed to the digest. + /// + /// + /// This method does not reset the stream position. After completion, the stream is positioned at end-of-stream. + /// + /// + /// A pooled buffer is used to reduce allocations. The buffer is returned to the pool even if an exception occurs. + /// + /// + private static async Task ComputeDigestAsync(Stream stream, IDigest digest, CancellationToken cancellationToken) + { + var buffer = ArrayPool.Shared.Rent(64 * 1024); + try + { + while (true) + { + cancellationToken.ThrowIfCancellationRequested(); + + var read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken).ConfigureAwait(false); + if (read <= 0) break; + + digest.BlockUpdate(buffer, 0, read); + } + } + finally + { + ArrayPool.Shared.Return(buffer); + } + } + + /// + /// Compares two hexadecimal strings for equality, ignoring case and surrounding whitespace. + /// + /// The first hexadecimal string. + /// The second hexadecimal string. + /// + /// if both strings are non-null and represent the same sequence of hex characters + /// (case-insensitive, trimmed); otherwise . + /// + /// + /// This helper is intentionally permissive to tolerate differences in casing and incidental whitespace + /// in descriptor files. + /// + private static bool HexEquals(string a, string b) + { + if (a is null || b is null) return false; + + return string.Equals(a.Trim(), b.Trim(), StringComparison.OrdinalIgnoreCase); + } + + /// + /// Converts the provided bytes to a lowercase hexadecimal string without separators. + /// + /// The bytes to encode as hexadecimal. + /// + /// A lowercase hexadecimal string whose length is bytes.Length * 2. + /// + /// + /// This method performs no allocations besides the resulting string and does not use culture-sensitive formatting. + /// + private static string ToLowerHex(ReadOnlySpan bytes) + { + // 2 chars per byte. + var chars = new char[bytes.Length * 2]; + var c = 0; + + for (var i = 0; i < bytes.Length; i++) + { + var b = bytes[i]; + chars[c++] = GetLowerHexNibble(b >> 4); + chars[c++] = GetLowerHexNibble(b & 0xF); + } + + return new string(chars); + + static char GetLowerHexNibble(int value) + => (char)(value < 10 ? ('0' + value) : ('a' + (value - 10))); + } + } +} diff --git a/SysML2.NET.Kpar/Cryptography/IChecksumService.cs b/SysML2.NET.Kpar/Cryptography/IChecksumService.cs new file mode 100644 index 000000000..da4547194 --- /dev/null +++ b/SysML2.NET.Kpar/Cryptography/IChecksumService.cs @@ -0,0 +1,75 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Kpar.Cryptography +{ + using System; + using System.Collections.Generic; + using System.IO.Compression; + using System.Threading; + using System.Threading.Tasks; + using SysML2.NET.ModelInterchange; + + /// + /// Provides checksum computation and validation utilities for .kpar archives. + /// + /// + /// This service: + /// + /// Computes checksums for supported values. + /// Validates the map against the actual ZIP entry contents. + /// Uses BouncyCastle for most digest algorithms and a built-in implementation for . + /// + /// + /// Algorithms that are not available in the current dependency set will raise . + /// + /// + public interface IChecksumService + { + /// + /// Validates all checksum entries in against the corresponding ZIP entries. + /// + /// The open ZIP archive containing the .kpar entries. + /// The parsed .meta.json metadata. + /// + /// Controls what happens when mismatches are detected. The default is . + /// + /// + /// A read-only list of detected mismatches. If is , + /// this method throws instead of returning a non-empty list. + /// + IReadOnlyList Validate(ZipArchive zip, InterchangeProjectMetadata metadata, ChecksumFailureBehavior behavior = ChecksumFailureBehavior.Throw); + + /// + /// Asynchronously validates all checksum entries in against the corresponding ZIP entries. + /// + /// The open ZIP archive containing the .kpar entries. + /// The parsed .meta.json metadata. + /// + /// Controls what happens when mismatches are detected. The default is . + /// + /// A token used to cancel the operation. + /// + /// A task that returns a read-only list of detected mismatches. If is , + /// this method throws instead of returning a non-empty list. + /// + Task> ValidateAsync(ZipArchive zip, InterchangeProjectMetadata metadata, ChecksumFailureBehavior behavior = ChecksumFailureBehavior.Throw, CancellationToken cancellationToken = default); + } +} diff --git a/SysML2.NET.Kpar/DictionaryExtensions.cs b/SysML2.NET.Kpar/DictionaryExtensions.cs new file mode 100644 index 000000000..5c52acd54 --- /dev/null +++ b/SysML2.NET.Kpar/DictionaryExtensions.cs @@ -0,0 +1,52 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Kpar +{ + using System; + using System.Collections.Generic; + using System.IO.Compression; + using System.Linq; + + /// + /// Extension methods for used by the .kpar ZIP reader. + /// + internal static class DictionaryExtensions + { + /// + /// Removes all entries whose key ends with the specified suffix, using an ordinal case-insensitive comparison. + /// + /// + /// The dictionary from which entries will be removed. + /// + /// + /// The suffix to match against dictionary keys (for example .project.json or .meta.json). + /// + /// + /// This method enumerates the dictionary keys to compute the removal set first, then removes matching entries. + /// This avoids modifying the dictionary while iterating it. + /// + internal static void RemoveWhereKeyEndsWith(this Dictionary dictionary, string suffix) + { + var keys = dictionary.Keys.Where(k => k.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)).ToList(); + foreach (var k in keys) dictionary.Remove(k); + } + } +} diff --git a/SysML2.NET.Kpar/IReader.cs b/SysML2.NET.Kpar/IReader.cs new file mode 100644 index 000000000..704fe70a2 --- /dev/null +++ b/SysML2.NET.Kpar/IReader.cs @@ -0,0 +1,130 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Kpar +{ + using System.IO; + using System.Threading; + using System.Threading.Tasks; + + using SysML2.NET.ModelInterchange; + + /// + /// Reads KerML Project Archives (.kpar) from a file path or stream. + /// + /// + /// A reader is expected to: + /// + /// Open the ZIP container. + /// Locate and parse .project.json and .meta.json at archive root. + /// Expose model interchange files (which may reside in subfolders) by their archive-relative paths. + /// + /// + public interface IReader + { + /// + /// Reads a .kpar file from a file path. + /// + /// Absolute or relative path to the archive. + /// Optional read options. + /// A parsed . + Archive Read(string filePath, ReadOptions options = null); + + /// + /// Reads a .kpar from an input stream. + /// + /// The stream containing the ZIP archive. + /// Optional read options. + /// A parsed . + Archive Read(Stream source, ReadOptions options = null); + + /// + /// Reads a .kpar file from a file path asynchronously. + /// + /// Absolute or relative path to the archive. + /// Optional read options. + /// Cancellation token. + /// A parsed . + Task ReadAsync(string filePath, ReadOptions options = null, CancellationToken cancellationToken = default); + + /// + /// Reads a .kpar from an input stream asynchronously. + /// + /// The stream containing the ZIP archive. + /// Optional read options. + /// Cancellation token. + /// A parsed . + Task ReadAsync(Stream source, ReadOptions options = null, CancellationToken cancellationToken = default); + + /// + /// Opens a .kpar file and returns an that keeps the underlying + /// ZIP container open for on-demand access to model and entry streams. + /// + /// + /// The absolute or relative file system path to the .kpar archive. + /// + /// + /// Optional controlling descriptor validation, + /// index validation, and other read-time behavior. + /// + /// + /// An containing the parsed + /// representation and providing methods to open model or entry streams on demand. + /// The caller is responsible for disposing the session. + /// + ArchiveSession Open(string filePath, ReadOptions options = null); + + /// + /// Opens a .kpar from an input stream and returns an that keeps the underlying + /// ZIP container open for on-demand content access. + /// + /// The stream containing the ZIP archive. + /// Optional read options. + /// + /// An containing the parsed and providing + /// methods to open entry/model streams. The caller must dispose the session. + /// + ArchiveSession Open(Stream source, ReadOptions options = null); + + /// + /// Asynchronously opens a .kpar file and returns an that keeps the underlying + /// ZIP container open for on-demand content access. + /// + /// Absolute or relative path to the archive. + /// Optional read options. + /// Cancellation token. + /// + /// A task that returns an . The caller must dispose the session. + /// + Task OpenAsync(string filePath, ReadOptions options = null, CancellationToken cancellationToken = default); + + /// + /// Asynchronously opens a .kpar from an input stream and returns an that keeps the underlying + /// ZIP container open for on-demand content access. + /// + /// The stream containing the ZIP archive. + /// Optional read options. + /// Cancellation token. + /// + /// A task that returns an . The caller must dispose the session. + /// + Task OpenAsync(Stream source, ReadOptions options = null, CancellationToken cancellationToken = default); + } +} diff --git a/SysML2.NET.Kpar/IWriter.cs b/SysML2.NET.Kpar/IWriter.cs new file mode 100644 index 000000000..b572b382d --- /dev/null +++ b/SysML2.NET.Kpar/IWriter.cs @@ -0,0 +1,78 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Kpar +{ + using System.IO; + using System.Threading; + using System.Threading.Tasks; + + using SysML2.NET.ModelInterchange; + + /// + /// Writes KerML Project Archives (.kpar) to a file path or stream. + /// + /// + /// A writer is expected to: + /// + /// Create a ZIP archive. + /// Write exactly one .project.json and one .meta.json at archive root. + /// Write one model interchange file per root namespace, with stable archive-relative paths. + /// + /// + public interface IWriter + { + /// + /// Writes a to a file path. + /// + /// Destination path (typically ending in .kpar). + /// The archive to write. + /// Optional write options. + void Write(string filePath, Archive archive, WriteOptions options = null); + + /// + /// Writes a to an output stream. + /// + /// Destination stream for the ZIP archive. + /// The archive to write. + /// If true, the writer does not dispose the stream. + /// Optional write options. + void Write(Stream destination, Archive archive, bool leaveOpen = false, WriteOptions options = null); + + /// + /// Writes a to a file path asynchronously. + /// + /// Destination path (typically ending in .kpar). + /// The package to write. + /// Optional write options. + /// Cancellation token. + Task WriteAsync(string filePath, Archive archive, WriteOptions options = null, CancellationToken cancellationToken = default); + + /// + /// Writes a to an output stream asynchronously. + /// + /// Destination stream for the ZIP archive. + /// The archive to write. + /// If true, the writer does not dispose the stream. + /// Optional write options. + /// Cancellation token. + Task WriteAsync(Stream destination, Archive archive, bool leaveOpen = false, WriteOptions options = null, CancellationToken cancellationToken = default); + } +} diff --git a/SysML2.NET.Kpar/ReadOptions.cs b/SysML2.NET.Kpar/ReadOptions.cs new file mode 100644 index 000000000..224d21432 --- /dev/null +++ b/SysML2.NET.Kpar/ReadOptions.cs @@ -0,0 +1,56 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Kpar +{ + using System; + + /// + /// Options controlling .kpar reading behavior. + /// + public sealed class ReadOptions + { + /// + /// If true, the reader validates that .project.json and .meta.json exist at archive root and are unique. + /// + public bool ValidateRequiredDescriptors { get; set; } = true; + + /// + /// If true, the reader ensures the index entries in metadata resolve to existing model files. + /// + public bool ValidateIndexPaths { get; set; } = true; + + /// + /// If true, model file checksums declared in .meta.json + /// are computed and validated. + /// + public bool ValidateChecksums { get; set; } = false; + + /// + /// Controls how checksum mismatches are handled when + /// is enabled. + /// + /// + /// Default behavior is , + /// causing archive opening to fail immediately on integrity violation. + /// + public ChecksumFailureBehavior ChecksumFailureBehavior { get; set; } = ChecksumFailureBehavior.Throw; + } +} diff --git a/SysML2.NET.Kpar/Reader.cs b/SysML2.NET.Kpar/Reader.cs new file mode 100644 index 000000000..4c7062953 --- /dev/null +++ b/SysML2.NET.Kpar/Reader.cs @@ -0,0 +1,995 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Kpar +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.IO.Compression; + using System.Linq; + using System.Text.Json; + using System.Threading; + using System.Threading.Tasks; + + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Logging.Abstractions; + + using SysML2.NET.Kpar.Cryptography; + using SysML2.NET.Extensions.Utilities; + using SysML2.NET.ModelInterchange; + using SysML2.NET.Serializer.Json.ModelInterchange; + + /// + /// Reads KerML Project Archives (.kpar) from a file path or stream. + /// + /// + /// A reader is expected to: + /// + /// Open the ZIP container. + /// Locate and parse .project.json and .meta.json at archive root. + /// Expose model interchange files (which may reside in subfolders) by their archive-relative paths. + /// + /// + public sealed class Reader : IReader + { + /// + /// The file name of the project descriptor located at the root of a .kpar archive. + /// + /// + /// The .project.json descriptor defines the logical project identity, + /// versioning, and structural metadata of the KerML archive. + /// + private const string ProjectDescriptorFileName = ".project.json"; + + /// + /// The file name of the metadata descriptor located at the root of a .kpar archive. + /// + /// + /// The .meta.json descriptor provides supplementary archive metadata, + /// including the model index and optional checksum information. + /// + private const string MetadataDescriptorFileName = ".meta.json"; + + /// + /// The default used when parsing descriptor files. + /// + /// + /// The reader: + /// + /// Skips JSON comments. + /// Allows trailing commas. + /// + /// These relaxed options improve interoperability with hand-authored or + /// tool-generated descriptor files while still enforcing structural correctness. + /// + private static readonly JsonReaderOptions DefaultReaderOptions = new() + { + CommentHandling = JsonCommentHandling.Skip, + AllowTrailingCommas = true + }; + + /// + /// The injected logger + /// + private readonly ILogger logger; + + /// + /// The injected + /// + private readonly IChecksumService checksumService; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The (injected) + /// + /// + /// The injected logger + /// + public Reader(IChecksumService checksumService, ILogger logger = null) + { + this.checksumService = checksumService; + this.logger = logger ?? NullLogger.Instance; + } + + /// + /// Gets the path to the Kpar file that has been read + /// + public string Path { get; internal set; } + + /// + /// Reads a KerML project archive from a file path. + /// + /// + /// The path to the .kpar file. + /// + /// + /// Optional read options. + /// The parsed . + /// + public Archive Read(string filePath, ReadOptions options = null) + { + if (string.IsNullOrEmpty(filePath)) + { + throw new ArgumentException("The path to the kpar file shall not be null or empty", nameof(filePath)); + } + + var sw = Stopwatch.StartNew(); + + this.logger.LogDebug("starting to read kpar at {Path}", filePath); + + options ??= new ReadOptions(); + using var fileStream = File.OpenRead(filePath); + + var archive = this.Read(fileStream, options); + + archive.Path = filePath; + + this.Path = filePath; + + this.logger.LogDebug("kpar at {Path} read in {ElapsedMilliseconds} [ms]", filePath, sw.ElapsedMilliseconds); + + return archive; + } + + /// + /// Reads a KerML project archive from a stream. + /// + /// + /// The stream containing the .kpar archive. + /// + /// + /// Optional read options. + /// + /// + /// The parsed . + /// + public Archive Read(Stream source, ReadOptions options = null) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source), "The source stream shall not be null"); + } + + var sw = Stopwatch.StartNew(); + + this.logger.LogDebug("starting to read kpar"); + + options ??= new ReadOptions(); + + using var zip = OpenZip(source); + var archive = ReadFromZip(zip, options); + + this.logger.LogDebug("kpar read in {ElapsedMilliseconds} [ms]", sw.ElapsedMilliseconds); + + return archive; + } + + /// + /// Reads a KerML project archive from a file path asynchronously. + /// + /// + /// The path to the .kpar file. + /// + /// + /// Optional read options. + /// + /// + /// The Cancellation token used to cancel the operation + /// + /// The parsed . + public async Task ReadAsync(string filePath, ReadOptions options = null, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(filePath)) + { + throw new ArgumentException("The path to the kpar file shall not be null or empty", nameof(filePath)); + } + + var sw = Stopwatch.StartNew(); + + this.logger.LogDebug("starting to read kpar at {Path}", filePath); + + options ??= new ReadOptions(); + + await using var fs = File.OpenRead(filePath); + + var archive = await this.ReadAsync(fs, options, cancellationToken).ConfigureAwait(false); + + archive.Path = filePath; + + this.Path = filePath; + + this.logger.LogDebug("kpar at {Path} read in {ElapsedMilliseconds} [ms]", filePath, sw.ElapsedMilliseconds); + + return archive; + } + + + /// + /// Reads a KerML project archive from a stream asynchronously. + /// + /// + /// The stream containing the .kpar archive. + /// + /// Optional read options. + /// + /// + /// The Cancellation token used to cancel the operation + /// + /// + /// The parsed . + /// + public async Task ReadAsync(Stream source, ReadOptions options = null, CancellationToken cancellationToken = default) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source), "The source stream shall not be null"); + } + + var sw = Stopwatch.StartNew(); + + this.logger.LogDebug("starting to read kpar"); + + options ??= new ReadOptions(); + + using var zip = OpenZip(source); + + var archive = await ReadFromZipAsync(zip, options, cancellationToken).ConfigureAwait(false); + + this.logger.LogDebug("kpar read in {ElapsedMilliseconds} [ms]", sw.ElapsedMilliseconds); + + return archive; + } + + /// + /// Opens a .kpar file and returns an that keeps the underlying + /// .kpar container open for on-demand access to model and entry streams. + /// + /// + /// The absolute or relative file system path to the .kpar archive. + /// + /// + /// Optional controlling descriptor validation, + /// index validation, and other read-time behavior. + /// + /// + /// An containing the parsed + /// representation and providing methods to open model or entry streams on demand. + /// The caller is responsible for disposing the session. + /// + public ArchiveSession Open(string filePath, ReadOptions options = null) + { + if (string.IsNullOrWhiteSpace(filePath)) + { + throw new ArgumentException("The path to the kpar file shall not be null or empty", nameof(filePath)); + } + + options ??= new ReadOptions(); + + var fs = File.OpenRead(filePath); + + try + { + var archiveSession = this.Open(fs, options); + + archiveSession.Archive.Path = filePath; + + WireModelEntryOpeners(archiveSession); + + return archiveSession; + } + catch + { + fs.Dispose(); + throw; + } + } + + /// + /// Opens a .kpar from an input stream and returns an that keeps the underlying + /// .kpar container open for on-demand content access. + /// + /// The stream containing the ZIP archive. + /// Optional read options. + /// + /// An containing the parsed and providing + /// methods to open entry/model streams. The caller must dispose the session. + /// + public ArchiveSession Open(Stream source, ReadOptions options = null) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source), "The source stream shall not be null"); + } + + options ??= new ReadOptions(); + + var zip = new ZipArchive(source, ZipArchiveMode.Read); + + try + { + var archive = ReadFromZip(zip, options); + + var archiveSession = new ArchiveSession(source, zip, archive); + + WireModelEntryOpeners(archiveSession); + + return archiveSession; + } + catch + { + zip.Dispose(); + source.Dispose(); + throw; + } + } + + /// + /// Asynchronously opens a .kpar file and returns an that keeps the underlying + /// .kpar container open for on-demand content access. + /// + /// Absolute or relative path to the archive. + /// Optional read options. + /// Cancellation token. + /// + /// A task that returns an . The caller must dispose the session. + /// + public async Task OpenAsync(string filePath, ReadOptions options = null, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(filePath)) + { + throw new ArgumentException("The path to the kpar file shall not be null or empty", nameof(filePath)); + } + + cancellationToken.ThrowIfCancellationRequested(); + options ??= new ReadOptions(); + + var fs = File.OpenRead(filePath); + + try + { + var archiveSession = await this.OpenAsync(fs, options, cancellationToken).ConfigureAwait(false); + + archiveSession.Archive.Path = filePath; + + WireModelEntryOpeners(archiveSession); + + return archiveSession; + } + catch + { + await fs.DisposeAsync().ConfigureAwait(false); + throw; + } + } + + /// + /// Asynchronously opens a .kpar from an input stream and returns an that keeps the underlying + /// ZIP container open for on-demand content access. + /// + /// The stream containing the ZIP archive. + /// Optional read options. + /// Cancellation token. + /// + /// A task that returns an . The caller must dispose the session. + /// + public async Task OpenAsync(Stream source, ReadOptions options = null, CancellationToken cancellationToken = default) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source), "The source stream shall not be null"); + } + + cancellationToken.ThrowIfCancellationRequested(); + options ??= new ReadOptions(); + + var zip = new ZipArchive(source, ZipArchiveMode.Read); + + try + { + var archive = await ReadFromZipAsync(zip, options, cancellationToken).ConfigureAwait(false); + + var archiveSession = new ArchiveSession(source, zip, archive); + + WireModelEntryOpeners(archiveSession); + + return archiveSession; + } + catch + { + zip.Dispose(); + + await source.DisposeAsync().ConfigureAwait(false); + + throw; + } + } + + /// + /// Reads an from the specified + /// using the provided . + /// + /// + /// The containing the archive data to read. + /// + /// + /// The that control how the archive contents + /// are interpreted, filtered, and materialized. + /// + /// + /// An instance representing the logical contents + /// of the ZIP archive. + /// + private Archive ReadFromZip(ZipArchive zip, ReadOptions options) + { + var projectEntry = FindDescriptorEntryAtRoot(zip, ProjectDescriptorFileName, options); + var metaEntry = FindDescriptorEntryAtRoot(zip, MetadataDescriptorFileName, options); + + var interchangeProject = ReadJsonEntry(projectEntry, static (ref Utf8JsonReader r) => InterchangeProjectDeSerializer.DeSerialize(ref r)); + + var interchangeProjectMetadata = ReadJsonEntry(metaEntry, static (ref Utf8JsonReader r) => InterchangeProjectMetadataDeSerializer.DeSerialize(ref r)); + + var checksumMismatches = this.ValidateChecksums(zip, interchangeProjectMetadata, options); + + var modelEntries = BuildModelEntries(zip, interchangeProjectMetadata, options); + + return new Archive + { + Project = interchangeProject, + Metadata = interchangeProjectMetadata, + Models = modelEntries, + ChecksumMismatches = checksumMismatches + }; + } + + /// + /// Reads and builds the from a ZIP container (asynchronous path). + /// + /// The that is to be read + /// The used to read + /// The Cancellation token used to cancel the operation + private async Task ReadFromZipAsync(ZipArchive zip, ReadOptions options, CancellationToken cancellationToken) + { + var projectEntry = FindDescriptorEntryAtRoot(zip, ProjectDescriptorFileName, options); + var metaEntry = FindDescriptorEntryAtRoot(zip, MetadataDescriptorFileName, options); + + var project = await ReadJsonEntryAsync(projectEntry, static (ref Utf8JsonReader utf8JsonReader) => InterchangeProjectDeSerializer.DeSerialize(ref utf8JsonReader), + cancellationToken) + .ConfigureAwait(false); + + var meta = await ReadJsonEntryAsync(metaEntry, static (ref Utf8JsonReader utf8JsonReader) => InterchangeProjectMetadataDeSerializer.DeSerialize(ref utf8JsonReader), + cancellationToken) + .ConfigureAwait(false); + + var checksumMismatches = await this.ValidateChecksumsAsync(zip, meta, options, cancellationToken) + .ConfigureAwait(false); + + var models = BuildModelEntries(zip, meta, options); + + return new Archive + { + Project = project, + Metadata = meta, + Models = models, + ChecksumMismatches = checksumMismatches + }; + } + + /// + /// Opens a ZIP archive in read mode. + /// + /// + /// The to read from + /// + private static ZipArchive OpenZip(Stream source) + { + return new ZipArchive(source, ZipArchiveMode.Read); + } + + /// + /// Finds a descriptor entry with the given file name at archive root. + /// + /// The ZIP archive to search. + /// The descriptor file name (e.g. .project.json). + /// Read options controlling validation behavior. + /// The matching , or if not found and validation is disabled. + /// + /// Thrown when descriptor validation is enabled and the descriptor is missing or duplicated. + /// + private static ZipArchiveEntry FindDescriptorEntryAtRoot(ZipArchive zip, string descriptorFileName, ReadOptions options) + { + ZipArchiveEntry result = null; + + foreach (var entry in zip.Entries) + { + if (!IsRootEntry(entry)) + { + continue; + } + + if (string.Equals(entry.Name, descriptorFileName, StringComparison.OrdinalIgnoreCase)) + { + if (result != null && options?.ValidateRequiredDescriptors == true) + { + throw new InvalidDataException($"Multiple {descriptorFileName} files found at archive root."); + } + + result = entry; + } + } + + if (result == null && options?.ValidateRequiredDescriptors == true) + { + throw new InvalidDataException($"{descriptorFileName} descriptor not found at archive root."); + } + + return result; + } + + /// + /// Determines whether the specified represents + /// a file located at the root level of the archive (i.e., not contained + /// within a subdirectory and not a directory entry). + /// + /// + /// The to evaluate. + /// + /// + /// if the entry is a non-directory file located + /// at the archive root; otherwise, . + /// + private static bool IsRootEntry(ZipArchiveEntry entry) + { + if (string.IsNullOrEmpty(entry.Name)) + { + return false; + } + + return entry.FullName.IndexOf('/') < 0; + } + + /// + /// Builds the set of instances contained in the specified + /// , using the metadata index when available. + /// + /// + /// The containing the model interchange entries. + /// + /// + /// The describing the archive contents, + /// including any index of model entries, if present. + /// + /// + /// The controlling how entries are discovered and filtered. + /// + /// + /// An array of instances representing the model files + /// included in the archive. + /// + private static ModelEntry[] BuildModelEntries(ZipArchive zip, InterchangeProjectMetadata meta, ReadOptions options) + { + var map = meta.Index; + + var entryByPath = zip.Entries + .Where(e => !e.FullName.EndsWith("/", StringComparison.Ordinal)) + .ToDictionary(e => e.FullName.NormalizeZipPath(), e => e, StringComparer.Ordinal); + + entryByPath.RemoveWhereKeyEndsWith(ProjectDescriptorFileName); + entryByPath.RemoveWhereKeyEndsWith(MetadataDescriptorFileName); + + var result = new List(map.Count > 0 ? map.Count : entryByPath.Count); + + if (map.Count > 0) + { + foreach (var kvp in map) + { + var modelPath = kvp.Value.NormalizeZipPath(); + + if (options.ValidateIndexPaths && !entryByPath.TryGetValue(modelPath, out _)) + { + throw new InvalidDataException($"Metadata index entry '{kvp.Key}' points to missing archive path '{modelPath}'."); + } + + if (!entryByPath.ContainsKey(modelPath)) + { + continue; + } + + result.Add(CreateModelEntry(zip, modelPath)); + } + + return result.ToArray(); + } + + foreach (var path in entryByPath.Keys.OrderBy(p => p, StringComparer.Ordinal)) + { + result.Add(CreateModelEntry(zip, path)); + } + + return result.ToArray(); + } + + /// + /// Creates a instance for the specified + /// normalized ZIP entry path within the given . + /// + /// + /// The containing the entry from which the + /// is created. + /// + /// + /// The normalized ZIP entry path identifying the archive entry. + /// The path is expected to use forward slashes ('/') as directory + /// separators and must not contain a leading "./" segment. + /// + /// + /// A representing the archive entry located at + /// . + /// + private static ModelEntry CreateModelEntry(ZipArchive zip, string normalizedPath) + { + var contentType = QueryContentType(normalizedPath); + + return new ModelEntry + { + Path = normalizedPath, + ContentType = contentType, + + OpenReadAsync = async (_) => + { + var entry = zip.GetEntry(normalizedPath); + if (entry is null) + { + throw new FileNotFoundException($"kpar entry '{normalizedPath}' not found."); + } + + var s = entry.Open(); + return await Task.FromResult(s).ConfigureAwait(false); + } + }; + } + + /// + /// Determines the MIME content type associated with the file extension + /// of the specified path. + /// + /// + /// The file path or file name whose extension is used to resolve + /// the corresponding MIME content type. + /// + /// + /// A string representing the MIME content type (for example, + /// application/json or application/zip) associated + /// with the file extension of . + /// + /// + /// Thrown if is . + /// + /// + /// Thrown if is empty or does not contain + /// a valid file name or extension, depending on the implementation. + /// + private static string QueryContentType(string path) + { + if (path.EndsWith(".json", StringComparison.OrdinalIgnoreCase)) return "application/json"; + if (path.EndsWith(".xml", StringComparison.OrdinalIgnoreCase)) return "application/xml"; + if (path.EndsWith(".kerml", StringComparison.OrdinalIgnoreCase)) return "text/plain"; + return null; + } + + /// + /// Reads a JSON entry from the specified and parses + /// its UTF-8 payload using the supplied low-level parser delegate. + /// + /// + /// The type of object produced by the . + /// + /// + /// The representing the JSON file to read and parse. + /// + /// + /// A delegate that parses the JSON payload using a ref-based + /// and materializes an instance of + /// . + /// + /// + /// An instance of parsed from the JSON content + /// of the specified ZIP entry. + /// + private static T ReadJsonEntry(ZipArchiveEntry entry, FuncRefReader parser) + { + using var stream = entry.Open(); + var bytes = stream.ReadAllBytes(); + return ParseJsonBytes(bytes, parser); + } + + /// + /// Asynchronously reads a JSON entry from the specified + /// and parses its UTF-8 payload using the supplied low-level parser delegate. + /// + /// + /// The type of object produced by the . + /// + /// + /// The representing the JSON file to read and parse. + /// + /// + /// A delegate that parses the JSON payload using a ref-based + /// and materializes an instance of + /// . + /// + /// + /// A used to cancel the asynchronous + /// read operation. + /// + /// + /// A task that represents the asynchronous operation. The task result contains + /// the parsed instance of . + /// + private static async Task ReadJsonEntryAsync(ZipArchiveEntry entry, FuncRefReader parser, CancellationToken ct) + { + await using var stream = entry.Open(); + var bytes = await stream.ReadAllBytesAsync(ct).ConfigureAwait(false); + return ParseJsonBytes(bytes, parser); + } + + /// + /// Parses a JSON payload from the provided UTF-8 encoded byte span using + /// a low-level, ref-based parser delegate. + /// + /// + /// The type of object produced by the supplied . + /// + /// + /// A of UTF-8 encoded JSON bytes representing + /// a single JSON value. + /// + /// + /// A delegate that receives a (passed by reference) + /// positioned at the beginning of the JSON payload and is responsible for + /// parsing and materializing an instance of . + /// + /// + /// An instance of produced by the + /// from the supplied JSON payload. + /// + /// + /// This method creates a over the provided byte span + /// without additional allocations. The reader is forward-only and must be + /// fully consumed by the implementation according + /// to the parsing contract. + /// + /// The input is expected to contain valid UTF-8 encoded JSON. Validation and + /// structural correctness are enforced by . + /// + private static T ParseJsonBytes(ReadOnlySpan bytes, FuncRefReader parser) + { + var reader = new Utf8JsonReader(bytes, DefaultReaderOptions); + + if (!reader.Read()) + { + throw new JsonException("Unexpected end of JSON payload."); + } + + return parser(ref reader); + } + + /// + /// Wires delegates for all instances + /// in the specified so that entry content streams can be opened on demand. + /// + /// + /// The active that owns the underlying + /// and backing . + /// + private static void WireModelEntryOpeners(ArchiveSession session) + { + if (session?.Archive?.Models is null) return; + + foreach (var model in session.Archive.Models) + { + if (model is null) continue; + + var path = model.Path; + + model.OpenReadAsync = ct => + { + ct.ThrowIfCancellationRequested(); + + var s = session.OpenEntry(path); + return new ValueTask(Task.FromResult(s)); + }; + } + } + + /// + /// Validates model file checksums declared in the archive metadata against the actual + /// contents of the provided . + /// + /// + /// The open representing the underlying .kpar container. + /// The archive must remain valid and readable for the duration of validation. + /// + /// + /// The parsed instance originating from + /// .meta.json. This metadata may contain a checksum map describing expected + /// hash values for model entries. + /// + /// + /// The controlling checksum validation behavior. + /// Validation is performed only when is + /// set to . + /// The handling of detected mismatches is governed by + /// . + /// + /// + /// A read-only collection of instances representing + /// detected checksum inconsistencies. + /// + /// If checksum validation is disabled or the metadata does not declare any checksums, + /// an empty collection is returned. + /// + /// + private IReadOnlyList ValidateChecksums(ZipArchive zip, InterchangeProjectMetadata meta, ReadOptions options) + { + if (options?.ValidateChecksums != true) return Array.Empty(); + + var checksums = meta?.Checksum; + if (checksums is null || checksums.Count == 0) return Array.Empty(); + + var behavior = options.ChecksumFailureBehavior; + + var mismatches = this.checksumService.Validate(zip, meta, behavior); + + HydrateMismatchIndexKeys(meta, mismatches); + + return mismatches; + } + + /// + /// Asynchronously validates all model file checksums declared in the specified + /// against the actual contents of the + /// provided . + /// + /// + /// The open representing the .kpar container. + /// The archive must remain open for the duration of the validation process. + /// + /// + /// The parsed containing the + /// checksum declarations from .meta.json. + /// + /// + /// The controlling checksum validation behavior. + /// This must be non-null. The flag + /// determines whether validation is enabled, and + /// determines how mismatches + /// are handled. + /// + /// + /// A that can be used to cancel the asynchronous + /// validation operation. + /// + /// + /// A task representing the asynchronous validation operation. The task result + /// contains a read-only list of detected instances. + /// If no mismatches are detected, an empty list is returned. + /// + /// + /// Thrown if , , or + /// is . + /// + /// + /// Thrown if a checksum declaration refers to a ZIP entry that does not exist + /// within the archive. + /// + /// + /// Thrown when one or more checksum mismatches are detected and + /// is set to + /// . + /// + private async Task> ValidateChecksumsAsync(ZipArchive zip, InterchangeProjectMetadata meta, ReadOptions options, CancellationToken ct) + { + if (options?.ValidateChecksums != true) return Array.Empty(); + + var checksums = meta?.Checksum; + if (checksums is null || checksums.Count == 0) return Array.Empty(); + + var behavior = options.ChecksumFailureBehavior; + + var mismatches = await this.checksumService.ValidateAsync(zip, meta, behavior, ct).ConfigureAwait(false); + + HydrateMismatchIndexKeys(meta, mismatches); + + return mismatches; + } + + /// + /// Enriches the specified collection of instances + /// with metadata index keys derived from the provided + /// . + /// + /// + /// The containing the + /// index map that associates logical model index keys + /// (for example "Base") with archive-relative paths. + /// + /// + /// The collection of instances + /// produced during checksum validation. + /// + private static void HydrateMismatchIndexKeys(InterchangeProjectMetadata meta, IReadOnlyList mismatches) + { + if (mismatches is null || mismatches.Count == 0) return; + + var index = meta?.Index; + if (index is null || index.Count == 0) return; + + var byPath = new Dictionary(StringComparer.Ordinal); + foreach (var kvp in index) + { + var p = kvp.Value?.NormalizeZipPath(); + if (string.IsNullOrWhiteSpace(p)) continue; + + if (!byPath.ContainsKey(p)) + { + byPath[p] = kvp.Key; + } + } + + foreach (var m in mismatches) + { + if (m is null) continue; + + if (!string.IsNullOrWhiteSpace(m.Path) && byPath.TryGetValue(m.Path.NormalizeZipPath(), out var key)) + { + m.IndexKey = key; + } + } + } + + /// + /// Represents a delegate that parses a JSON value using a forward-only, + /// ref-based . + /// + /// + /// The type of the object produced from the JSON input. + /// + /// + /// A reference to the positioned at the beginning + /// of the JSON value to be parsed. The reader is passed by reference because it is a + /// ref struct and because parsing advances its internal state. + /// + /// + /// An instance of created from the JSON value read + /// from the provided . + /// + /// + /// Implementations are expected to fully consume the JSON value they parse, + /// leaving the positioned on the final token of that value. + /// The caller remains responsible for advancing the reader beyond that token, + /// if required. + /// + private delegate T FuncRefReader(ref Utf8JsonReader reader); + } +} diff --git a/SysML2.NET.Kpar/SysML2.NET.Kpar.csproj b/SysML2.NET.Kpar/SysML2.NET.Kpar.csproj new file mode 100644 index 000000000..020b0d160 --- /dev/null +++ b/SysML2.NET.Kpar/SysML2.NET.Kpar.csproj @@ -0,0 +1,45 @@ + + + + + + netstandard2.1 + 12.0 + 0.18.0 + A .NET implementation of the OMG SysML v2 specification. + SysML2.NET.Kpar + Starion Group S.A. + Copyright © Starion Group S.A. + Apache-2.0 + https://github.com/STARIONGROUP/SysML2.NET.git + Git + Sam Gerené + true + + [Initial] Version + + cdp4-icon.png + README.md + true + true + + + + + + + + + + + + + + + + + + + + + diff --git a/SysML2.NET.Kpar/WriteOptions.cs b/SysML2.NET.Kpar/WriteOptions.cs new file mode 100644 index 000000000..c3aaf84aa --- /dev/null +++ b/SysML2.NET.Kpar/WriteOptions.cs @@ -0,0 +1,48 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Kpar +{ + /// + /// Options controlling .kpar writing behavior. + /// + public sealed class WriteOptions + { + /// + /// If true, the writer normalizes paths to use forward slashes and strips leading slashes. + /// + public bool NormalizePaths { get; set; } = true; + + /// + /// If true, the writer fails if any descriptor would be written outside the archive root. + /// + public bool EnforceDescriptorsAtRoot { get; set; } = true; + + /// + /// Optional: compute and populate metadata checksums for some or all model files. + /// + public bool ComputeChecksums { get; set; } = false; + + /// + /// Optional checksum algorithm name (e.g., SHA256) if is enabled. + /// + public string ChecksumAlgorithm { get; set; } + } +} diff --git a/SysML2.NET.Serializer.Json.Tests/Data/.meta.json b/SysML2.NET.Serializer.Json.Tests/Data/.meta.json new file mode 100644 index 000000000..565d6eb6e --- /dev/null +++ b/SysML2.NET.Serializer.Json.Tests/Data/.meta.json @@ -0,0 +1,90 @@ +{ + "index": { + "Base": "Base.kerml", + "Clocks": "Clocks.kerml", + "ControlPerformances": "ControlPerformances.kerml", + "FeatureReferencingPerformances": "FeatureReferencingPerformances.kerml", + "KerML": "KerML.kerml", + "Links": "Links.kerml", + "Metaobjects": "Metaobjects.kerml", + "Objects": "Objects.kerml", + "Observation": "Observation.kerml", + "Occurrences": "Occurrences.kerml", + "Performances": "Performances.kerml", + "SpatialFrames": "SpatialFrames.kerml", + "StatePerformances": "StatePerformances.kerml", + "Transfers": "Transfers.kerml", + "TransitionPerformances": "TransitionPerformances.kerml", + "Triggers": "Triggers.kerml" + }, + "created": "2025-03-13T00:00:00Z", + "metamodel": "https://www.omg.org/spec/KerML/20250201", + "includesDerived": false, + "includesImplied": false, + "checksum": { + "Triggers.kerml": { + "value": "124cad3625935e078d1363e6100ee12537ca9c51445a18108e056db8b4885609", + "algorithm": "SHA256" + }, + "ControlPerformances.kerml": { + "value": "31385be7dca94bd0538f011d5c8f7925626d54f96970769f0fdb28b2186a9a03", + "algorithm": "SHA256" + }, + "Transfers.kerml": { + "value": "fa40b483a7834d89f07aad0f6f57e79244adc2a58b4396c4734bddeb297d7c46", + "algorithm": "SHA256" + }, + "Objects.kerml": { + "value": "9057e2781fe8793d5108973c0647318caa26310be6231c6380152a4cbc894c25", + "algorithm": "SHA256" + }, + "Metaobjects.kerml": { + "value": "983dbd85a4b183d8859326ee512fc59d991fb98a115e009e72fad21d1f9d1685", + "algorithm": "SHA256" + }, + "Performances.kerml": { + "value": "fd965e184b300737a192530de0c800cdbee236cb6220612f370400da21dfb327", + "algorithm": "SHA256" + }, + "StatePerformances.kerml": { + "value": "f02fb7e8de58f4304c95c575ee1bcb7d271d621ce8e336ce36ea80a4e956c3da", + "algorithm": "SHA256" + }, + "Base.kerml": { + "value": "56df84cda67f62c63d4e79e2786fc26046cfa361a958c4fcf0843d32a5707e09", + "algorithm": "SHA256" + }, + "Observation.kerml": { + "value": "6bc57a73c43af6f61201b6eb659024a9f08f974643eb5a101e068e3637761ee4", + "algorithm": "SHA256" + }, + "TransitionPerformances.kerml": { + "value": "1ce78437c817c8359a2cad43e8e72b23dd32b81d2a69dc1126c803fae72aae70", + "algorithm": "SHA256" + }, + "FeatureReferencingPerformances.kerml": { + "value": "b6f9e5349c7c7f393591c0334c3bec86f1766b3e37209819179310c2f8fe1fb7", + "algorithm": "SHA256" + }, + "KerML.kerml": { + "value": "8fdf4b7416e981c895cd74b75dc14b18091d13cbcaff7cdded6f9c23e2483d58", + "algorithm": "SHA256" + }, + "Occurrences.kerml": { + "value": "b3a62ce0bc3a4f7e667102b4c2f68a4928ca8efeda425c6a4c8bdeadfbc9bbc1", + "algorithm": "SHA256" + }, + "Clocks.kerml": { + "value": "960ac0884935e308beea55c78ed11b6946c37a386eb7958ef2c913aa275ae4c7", + "algorithm": "SHA256" + }, + "Links.kerml": { + "value": "dcf3c002717cb91f8e16f1890fdf5526f4e178ada898a189621c7d0c24b5ddc0", + "algorithm": "SHA256" + }, + "SpatialFrames.kerml": { + "value": "2a7790ebc2afacbd64eb781567906921e38eff385c917be03b090b8289353de7", + "algorithm": "SHA256" + } + } +} \ No newline at end of file diff --git a/SysML2.NET.Serializer.Json.Tests/Data/.project.json b/SysML2.NET.Serializer.Json.Tests/Data/.project.json new file mode 100644 index 000000000..5e92d19b6 --- /dev/null +++ b/SysML2.NET.Serializer.Json.Tests/Data/.project.json @@ -0,0 +1,19 @@ +{ + "name": "Kernel Semantic Library", + "description": "Standard semantic library for the Kernel Modeling Language (KerML)", + "version": "1.0.0", + "license": "LGPL", + "maintainer": ["OMG"], + "website": "https://www.omg.org/spec/KerML", + "topic": ["Kerml", "OMG"], + "usage": [ + { + "resource": "https://www.omg.org/spec/KerML/20250201/Data-Type-Library.kpar", + "versionConstraint": "1.0.0" + }, + { + "resource": "https://www.omg.org/spec/KerML/20250201/Function-Library.kpar", + "versionConstraint": "1.0.0" + } + ] +} \ No newline at end of file diff --git a/SysML2.NET.Serializer.Json.Tests/ModelInterchange/InterchangeChecksumDeserializerTestFixture.cs b/SysML2.NET.Serializer.Json.Tests/ModelInterchange/InterchangeChecksumDeserializerTestFixture.cs new file mode 100644 index 000000000..2114d1f78 --- /dev/null +++ b/SysML2.NET.Serializer.Json.Tests/ModelInterchange/InterchangeChecksumDeserializerTestFixture.cs @@ -0,0 +1,201 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Serializer.Json.Tests.ModelInterchange +{ + using System; + using System.Buffers; + using System.Text; + using System.Text.Json; + + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Logging.Abstractions; + using NUnit.Framework; + + using SysML2.NET.ModelInterchange; + using SysML2.NET.Serializer.Json.ModelInterchange; + + [TestFixture] + public class InterchangeChecksumDeserializerTestFixture + { + [Test] + public void Verify_that_DeSerialize_reads_value_and_algorithm_from_valid_object() + { + var json = @"{""value"":""ABCDEF0123456789"",""algorithm"":""SHA256""}"; + + var checksum = DeserializeFromJson(json); + + Assert.That(checksum, Is.Not.Null); + Assert.That(checksum.Value, Is.EqualTo("ABCDEF0123456789")); + Assert.That(checksum.Algorithm, Is.EqualTo(ChecksumKind.SHA256)); + } + + [Test] + public void Verify_that_DeSerialize_tolerates_null_value_and_sets_empty_string() + { + var json = @"{""value"":null,""algorithm"":""SHA1""}"; + + var checksum = DeserializeFromJson(json); + + Assert.That(checksum.Value, Is.EqualTo(string.Empty)); + Assert.That(checksum.Algorithm, Is.EqualTo(ChecksumKind.SHA1)); + } + + [Test] + public void Verify_that_DeSerialize_tolerates_null_algorithm_and_leaves_default() + { + var json = @"{""value"":""deadbeef"",""algorithm"":null}"; + + var checksum = DeserializeFromJson(json); + + Assert.That(checksum.Value, Is.EqualTo("deadbeef")); + Assert.That(checksum.Algorithm, Is.EqualTo(default(ChecksumKind))); + } + + [Test] + public void Verify_that_DeSerialize_skips_unknown_properties_and_continues() + { + var json = @"{ + ""value"":""1234"", + ""unknownString"":""x"", + ""unknownObj"":{""a"":1,""b"":[true,false]}, + ""unknownArr"":[1,2,3], + ""algorithm"":""MD5"" + }"; + + var checksum = DeserializeFromJson(json, NullLoggerFactory.Instance); + + Assert.That(checksum.Value, Is.EqualTo("1234")); + Assert.That(checksum.Algorithm, Is.EqualTo(ChecksumKind.MD5)); + } + + [Test] + public void Verify_that_DeSerialize_skips_non_string_algorithm_and_leaves_default() + { + var json = @"{""value"":""1234"",""algorithm"":123}"; + + var checksum = DeserializeFromJson(json); + + Assert.That(checksum.Value, Is.EqualTo("1234")); + Assert.That(checksum.Algorithm, Is.EqualTo(default(ChecksumKind))); + } + + [Test] + public void Verify_that_DeSerialize_reads_algorithm_from_multi_segment_value_sequence() + { + // Force HasValueSequence = true by using a segmented ReadOnlySequence. + // We craft JSON so that the algorithm token ("SHA256") spans segments. + + var utf8 = Encoding.UTF8.GetBytes(@"{""value"":""v"",""algorithm"":""SHA256""}"); + + // Split *inside* the algorithm string bytes so the ValueSequence is multi-segment. + // Find the "SHA256" bytes and split after "SHA". + var needle = Encoding.UTF8.GetBytes(@"""SHA256"""); + var idx = IndexOf(utf8, needle); + Assert.That(idx, Is.GreaterThanOrEqualTo(0), "Test setup failed: could not find algorithm token."); + + // Position of actual letters starts after the first quote + var startLetters = idx + 1; + var splitAt = startLetters + 3; // "SHA" | "256" + + var sequence = CreateMultiSegmentSequence(utf8, splitAt); + + var reader = new Utf8JsonReader(sequence, isFinalBlock: true, state: default); + + // Move to StartObject + Assert.That(reader.Read(), Is.True); + Assert.That(reader.TokenType, Is.EqualTo(JsonTokenType.StartObject)); + + var checksum = InterchangeChecksumDeserializer.DeSerialize(ref reader); + + Assert.That(checksum.Value, Is.EqualTo("v")); + Assert.That(checksum.Algorithm, Is.EqualTo(ChecksumKind.SHA256)); + } + + private static InterchangeChecksum DeserializeFromJson(string json, ILoggerFactory loggerFactory = null) + { + var bytes = Encoding.UTF8.GetBytes(json); + var reader = new Utf8JsonReader(bytes, isFinalBlock: true, state: default); + + // Position the reader on StartObject (as required by the deserializer) + Assert.That(reader.Read(), Is.True, "JSON did not yield any tokens."); + Assert.That(reader.TokenType, Is.EqualTo(JsonTokenType.StartObject), "Reader not positioned on StartObject."); + + return InterchangeChecksumDeserializer.DeSerialize(ref reader, loggerFactory); + } + + private static int IndexOf(byte[] haystack, byte[] needle) + { + if (needle.Length == 0) return 0; + for (var i = 0; i <= haystack.Length - needle.Length; i++) + { + var match = true; + for (var j = 0; j < needle.Length; j++) + { + if (haystack[i + j] != needle[j]) + { + match = false; + break; + } + } + if (match) return i; + } + return -1; + } + + private static ReadOnlySequence CreateMultiSegmentSequence(byte[] data, int splitIndex) + { + if (splitIndex <= 0 || splitIndex >= data.Length) + { + throw new ArgumentOutOfRangeException(nameof(splitIndex)); + } + + var firstMem = new ReadOnlyMemory(data, 0, splitIndex); + var secondMem = new ReadOnlyMemory(data, splitIndex, data.Length - splitIndex); + + var first = new BufferSegment(firstMem); + var last = first.Append(secondMem); + + return new ReadOnlySequence(first, 0, last, last.Memory.Length); + } + + /// + /// Minimal ReadOnlySequence segment helper to build multi-segment sequences. + /// + private sealed class BufferSegment : ReadOnlySequenceSegment + { + public BufferSegment(ReadOnlyMemory memory) + { + Memory = memory; + } + + public BufferSegment Append(ReadOnlyMemory memory) + { + var segment = new BufferSegment(memory) + { + RunningIndex = RunningIndex + Memory.Length + }; + + Next = segment; + return segment; + } + } + } +} diff --git a/SysML2.NET.Serializer.Json.Tests/ModelInterchange/InterchangeProjectDeSerializerTestFixture.cs b/SysML2.NET.Serializer.Json.Tests/ModelInterchange/InterchangeProjectDeSerializerTestFixture.cs new file mode 100644 index 000000000..44d419356 --- /dev/null +++ b/SysML2.NET.Serializer.Json.Tests/ModelInterchange/InterchangeProjectDeSerializerTestFixture.cs @@ -0,0 +1,88 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Serializer.Json.Tests.ModelInterchange +{ + using System.IO; + using System.Text.Json; + + using SysML2.NET.Serializer.Json.ModelInterchange; + using SysML2.NET.ModelInterchange; + + using NUnit.Framework; + + /// + /// Suite of tests for the + /// + [TestFixture] + public class InterchangeProjectDeSerializerTestFixture + { + [Test] + public void Verify_that_project_dot_json_file_can_be_deserialized() + { + var fileName = Path.Combine(TestContext.CurrentContext.WorkDirectory, "Data", ".project.json"); + using var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read); + + var json = File.ReadAllBytes(fileName); + + var interchangeProject = DeserializeFromJson(json); + + using (Assert.EnterMultipleScope()) + { + Assert.That(interchangeProject, Is.Not.Null); + Assert.That(interchangeProject.Name, Is.EqualTo("Kernel Semantic Library")); + Assert.That(interchangeProject.Description, Is.EqualTo("Standard semantic library for the Kernel Modeling Language (KerML)")); + Assert.That(interchangeProject.Version, Is.EqualTo("1.0.0")); + Assert.That(interchangeProject.License, Is.EqualTo("LGPL")); + Assert.That(interchangeProject.Maintainer.Count, Is.EqualTo(1)); + Assert.That(interchangeProject.Maintainer[0], Is.EqualTo("OMG")); + Assert.That(interchangeProject.Website, Is.EqualTo("https://www.omg.org/spec/KerML")); + Assert.That(interchangeProject.Topic.Count, Is.EqualTo(2)); + Assert.That(interchangeProject.Topic[0], Is.EqualTo("Kerml")); + Assert.That(interchangeProject.Topic[1], Is.EqualTo("OMG")); + Assert.That(interchangeProject.Usage.Count, Is.EqualTo(2)); + Assert.That(interchangeProject.Usage[0].Resource, Is.EqualTo("https://www.omg.org/spec/KerML/20250201/Data-Type-Library.kpar")); + Assert.That(interchangeProject.Usage[0].VersionConstraint, Is.EqualTo("1.0.0")); + Assert.That(interchangeProject.Usage[1].Resource, Is.EqualTo("https://www.omg.org/spec/KerML/20250201/Function-Library.kpar")); + Assert.That(interchangeProject.Usage[1].VersionConstraint, Is.EqualTo("1.0.0")); + } + } + + /// + /// Deserializes an instance from a UTF-8 encoded JSON payload. + /// + /// + /// A byte array containing a valid UTF-8 encoded JSON representation of an + /// document. + /// + /// + /// The deserialized instance. + /// + private static InterchangeProject DeserializeFromJson(byte[] json) + { + var reader = new Utf8JsonReader(json, isFinalBlock: true, state: default); + + Assert.That(reader.Read(), Is.True, "JSON did not yield any tokens."); + Assert.That(reader.TokenType, Is.EqualTo(JsonTokenType.StartObject), "Reader not positioned on StartObject."); + + return InterchangeProjectDeSerializer.DeSerialize(ref reader); + } + } +} diff --git a/SysML2.NET.Serializer.Json.Tests/ModelInterchange/InterchangeProjectMetadataDeSerializerTestFixture.cs b/SysML2.NET.Serializer.Json.Tests/ModelInterchange/InterchangeProjectMetadataDeSerializerTestFixture.cs new file mode 100644 index 000000000..4358ca471 --- /dev/null +++ b/SysML2.NET.Serializer.Json.Tests/ModelInterchange/InterchangeProjectMetadataDeSerializerTestFixture.cs @@ -0,0 +1,100 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Serializer.Json.Tests.ModelInterchange +{ + using System; + using System.IO; + using System.Text.Json; + + using SysML2.NET.Serializer.Json.ModelInterchange; + using SysML2.NET.ModelInterchange; + + using NUnit.Framework; + + /// + /// Suite of tests for the + /// + [TestFixture] + public class InterchangeProjectMetadataDeSerializerTestFixture + { + [Test] + public void Verify_that_meta_dot_json_file_can_be_deserialized() + { + var fileName = Path.Combine(TestContext.CurrentContext.WorkDirectory, "Data", ".meta.json"); + using var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read); + + var json = File.ReadAllBytes(fileName); + + var interchangeProjectMetadata = DeserializeFromJson(json); + + Assert.That(interchangeProjectMetadata, Is.Not.Null); + + Assert.Multiple(() => + { + Assert.That(interchangeProjectMetadata.Metamodel, Is.EqualTo("https://www.omg.org/spec/KerML/20250201")); + Assert.That(interchangeProjectMetadata.Created, Is.EqualTo(DateTimeOffset.Parse("2025-03-13T00:00:00Z", null, System.Globalization.DateTimeStyles.AssumeUniversal))); + Assert.That(interchangeProjectMetadata.IncludesDerived, Is.False); + Assert.That(interchangeProjectMetadata.IncludesImplied, Is.False); + + Assert.That(interchangeProjectMetadata.Index, Is.Not.Null); + Assert.That(interchangeProjectMetadata.Checksum, Is.Not.Null); + + Assert.That(interchangeProjectMetadata.Index, Has.Count.EqualTo(16)); + Assert.That(interchangeProjectMetadata.Index["Base"], Is.EqualTo("Base.kerml")); + Assert.That(interchangeProjectMetadata.Index["KerML"], Is.EqualTo("KerML.kerml")); + Assert.That(interchangeProjectMetadata.Index["Triggers"], Is.EqualTo("Triggers.kerml")); + + Assert.That(interchangeProjectMetadata.Checksum, Has.Count.EqualTo(16)); + + Assert.That(interchangeProjectMetadata.Checksum.ContainsKey("Base.kerml"), Is.True); + Assert.That(interchangeProjectMetadata.Checksum["Base.kerml"].Algorithm, Is.EqualTo(ChecksumKind.SHA256)); + Assert.That(interchangeProjectMetadata.Checksum["Base.kerml"].Value, Is.EqualTo("56df84cda67f62c63d4e79e2786fc26046cfa361a958c4fcf0843d32a5707e09")); + + Assert.That(interchangeProjectMetadata.Checksum.ContainsKey("Triggers.kerml"), Is.True); + Assert.That(interchangeProjectMetadata.Checksum["Triggers.kerml"].Algorithm, Is.EqualTo(ChecksumKind.SHA256)); + Assert.That(interchangeProjectMetadata.Checksum["Triggers.kerml"].Value, Is.EqualTo("124cad3625935e078d1363e6100ee12537ca9c51445a18108e056db8b4885609")); + + Assert.That(interchangeProjectMetadata.Checksum.ContainsKey("SpatialFrames.kerml"), Is.True); + Assert.That(interchangeProjectMetadata.Checksum["SpatialFrames.kerml"].Algorithm, Is.EqualTo(ChecksumKind.SHA256)); + Assert.That(interchangeProjectMetadata.Checksum["SpatialFrames.kerml"].Value, Is.EqualTo("2a7790ebc2afacbd64eb781567906921e38eff385c917be03b090b8289353de7")); + }); + } + + /// + /// Deserializes an instance from a UTF-8 encoded JSON payload. + /// + /// + /// The UTF-8 encoded JSON byte array representing a .meta.json interchange metadata document. + /// + /// + /// The deserialized instance. + /// + private static InterchangeProjectMetadata DeserializeFromJson(byte[] json) + { + var reader = new Utf8JsonReader(json, isFinalBlock: true, state: default); + + Assert.That(reader.Read(), Is.True, "JSON did not yield any tokens."); + Assert.That(reader.TokenType, Is.EqualTo(JsonTokenType.StartObject), "Reader not positioned on StartObject."); + + return InterchangeProjectMetadataDeSerializer.DeSerialize(ref reader); + } + } +} \ No newline at end of file diff --git a/SysML2.NET.Serializer.Json.Tests/SysML2.NET.Serializer.Json.Tests.csproj b/SysML2.NET.Serializer.Json.Tests/SysML2.NET.Serializer.Json.Tests.csproj index 141fc1756..a25aaaabb 100644 --- a/SysML2.NET.Serializer.Json.Tests/SysML2.NET.Serializer.Json.Tests.csproj +++ b/SysML2.NET.Serializer.Json.Tests/SysML2.NET.Serializer.Json.Tests.csproj @@ -67,6 +67,12 @@ Always + + Always + + + Always + diff --git a/SysML2.NET.Serializer.Json.Tests/Utility/Utf8JsonReaderHelperTestFixture.cs b/SysML2.NET.Serializer.Json.Tests/Utility/Utf8JsonReaderHelperTestFixture.cs new file mode 100644 index 000000000..f185f774a --- /dev/null +++ b/SysML2.NET.Serializer.Json.Tests/Utility/Utf8JsonReaderHelperTestFixture.cs @@ -0,0 +1,275 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Serializer.Json.Utility.Tests +{ + using System; + using System.Globalization; + using System.Text; + using System.Text.Json; + + using NUnit.Framework; + + using SysML2.NET.Serializer.Json.Utility; + + [TestFixture] + public sealed class Utf8JsonReaderHelperTestFixture + { + [Test] + public void Verify_that_Expect_does_not_throw_when_token_matches() + { + var reader = CreateReader("true"); + + Utf8JsonReaderHelper.Expect(ref reader, JsonTokenType.True); + + Assert.That(reader.TokenType, Is.EqualTo(JsonTokenType.True)); + } + + [Test] + public void Verify_that_Expect_throws_when_token_does_not_match() + { + Assert.That( + InvokeExpectWithFalse, + Throws.TypeOf().With.Message.Contains("Expected")); + + static void InvokeExpectWithFalse() + { + var reader = CreateReader("false"); + Utf8JsonReaderHelper.Expect(ref reader, JsonTokenType.True); + } + } + + [Test] + public void Verify_that_ReadStringOrNull_returns_null_when_token_is_null() + { + var reader = CreateReader("null"); + + var value = Utf8JsonReaderHelper.ReadStringOrNull(ref reader); + + Assert.That(value, Is.Null); + } + + [Test] + public void Verify_that_ReadStringOrNull_returns_string_when_token_is_string() + { + var reader = CreateReader("\"hello\""); + + var value = Utf8JsonReaderHelper.ReadStringOrNull(ref reader); + + Assert.That(value, Is.EqualTo("hello")); + } + + [Test] + public void Verify_that_ReadStringOrNull_throws_when_token_is_not_string_or_null() + { + Assert.That( + InvokeReadStringOrNullWithNumber, + Throws.TypeOf().With.Message.Contains("Expected string or null")); + + static void InvokeReadStringOrNullWithNumber() + { + var reader = CreateReader("123"); + _ = Utf8JsonReaderHelper.ReadStringOrNull(ref reader); + } + } + + [Test] + public void Verify_that_ReadBoolOrNull_returns_null_when_token_is_null() + { + var reader = CreateReader("null"); + + var value = Utf8JsonReaderHelper.ReadBoolOrNull(ref reader); + + Assert.That(value, Is.Null); + } + + [Test] + public void Verify_that_ReadBoolOrNull_returns_true_when_token_is_true() + { + var reader = CreateReader("true"); + + var value = Utf8JsonReaderHelper.ReadBoolOrNull(ref reader); + + Assert.That(value, Is.True); + } + + [Test] + public void Verify_that_ReadBoolOrNull_returns_false_when_token_is_false() + { + var reader = CreateReader("false"); + + var value = Utf8JsonReaderHelper.ReadBoolOrNull(ref reader); + + Assert.That(value, Is.False); + } + + [Test] + public void Verify_that_ReadBoolOrNull_throws_when_token_is_not_bool_or_null() + { + Assert.That( + InvokeReadBoolOrNullWithString, + Throws.TypeOf().With.Message.Contains("Expected bool or null")); + + static void InvokeReadBoolOrNullWithString() + { + var reader = CreateReader("\"true\""); + _ = Utf8JsonReaderHelper.ReadBoolOrNull(ref reader); + } + } + + [Test] + public void Verify_that_ReadDateTimeIso8601_parses_roundtrip_kind() + { + // Use a value that encodes offset to make RoundtripKind relevant. + var iso = "2026-02-15T12:34:56.789+01:00"; + var reader = CreateReader($"\"{iso}\""); + + var dt = Utf8JsonReaderHelper.ReadDateTimeIso8601(ref reader); + + var expected = DateTime.Parse(iso, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); + Assert.That(dt, Is.EqualTo(expected)); + } + + [TestCase("null")] + [TestCase("\"\"")] + [TestCase("\" \"")] + public void Verify_that_ReadDateTimeIso8601_throws_when_value_is_null_or_whitespace(string json) + { + Assert.That( + () => InvokeReadDateTimeIso8601(json), + Throws.TypeOf().With.Message.Contains("Expected ISO 8601 date-time string")); + } + + private static void InvokeReadDateTimeIso8601(string json) + { + var reader = CreateReader(json); + _ = Utf8JsonReaderHelper.ReadDateTimeIso8601(ref reader); + } + + [Test] + public void Verify_that_ReadDateTimeIso8601_throws_when_value_is_not_a_valid_datetime() + { + Assert.That( + InvokeReadDateTimeIso8601WithInvalidDate, + Throws.TypeOf()); + + static void InvokeReadDateTimeIso8601WithInvalidDate() + { + var reader = CreateReader("\"not-a-date\""); + _ = Utf8JsonReaderHelper.ReadDateTimeIso8601(ref reader); + } + } + + [Test] + public void Verify_that_ReadUriOrNull_returns_null_when_token_is_null() + { + var reader = CreateReader("null"); + + var uri = Utf8JsonReaderHelper.ReadUriOrNull(ref reader); + + Assert.That(uri, Is.Null); + } + + [TestCase("\"\"")] + [TestCase("\" \"")] + public void Verify_that_ReadUriOrNull_returns_null_when_string_is_empty_or_whitespace(string json) + { + var reader = CreateReader(json); + + var uri = Utf8JsonReaderHelper.ReadUriOrNull(ref reader); + + Assert.That(uri, Is.Null); + } + + [Test] + public void Verify_that_ReadUriOrNull_parses_absolute_uri() + { + var reader = CreateReader("\"https://example.com/a/b\""); + + var uri = Utf8JsonReaderHelper.ReadUriOrNull(ref reader); + + Assert.That(uri, Is.Not.Null); + Assert.That(uri.IsAbsoluteUri, Is.True); + Assert.That(uri.AbsoluteUri, Is.EqualTo("https://example.com/a/b")); + } + + [Test] + public void Verify_that_ReadUriOrNull_parses_relative_uri() + { + var reader = CreateReader("\"./folder/file.kerml\""); + + var uri = Utf8JsonReaderHelper.ReadUriOrNull(ref reader); + + Assert.That(uri, Is.Not.Null); + Assert.That(uri.IsAbsoluteUri, Is.False); + Assert.That(uri.OriginalString, Is.EqualTo("./folder/file.kerml")); + } + + [Test] + public void Verify_that_SkipValue_skips_nested_value_and_positions_reader_on_next_token() + { + // We position the reader on the value token (StartObject) of property "a", then skip it, + // and validate that we end up on the PropertyName token for "b". + var json = "{\"a\":{\"x\":[1,2,{\"y\":3}]},\"b\":42}"; + var reader = CreateReader(json); + + // StartObject (root) + Utf8JsonReaderHelper.Expect(ref reader, JsonTokenType.StartObject); + + // PropertyName "a" + Assert.That(reader.Read(), Is.True); + Assert.That(reader.TokenType, Is.EqualTo(JsonTokenType.PropertyName)); + Assert.That(reader.GetString(), Is.EqualTo("a")); + + // Value for "a" (StartObject) + Assert.That(reader.Read(), Is.True); + Assert.That(reader.TokenType, Is.EqualTo(JsonTokenType.StartObject)); + + Utf8JsonReaderHelper.SkipValue(ref reader); + + // After Skip(), reader is positioned on the last token of the skipped value (EndObject). + Assert.That(reader.TokenType, Is.EqualTo(JsonTokenType.EndObject)); + + // Next token should be PropertyName "b" + Assert.That(reader.Read(), Is.True); + Assert.That(reader.TokenType, Is.EqualTo(JsonTokenType.PropertyName)); + Assert.That(reader.GetString(), Is.EqualTo("b")); + + // And its value + Assert.That(reader.Read(), Is.True); + Assert.That(reader.TokenType, Is.EqualTo(JsonTokenType.Number)); + Assert.That(reader.GetInt32(), Is.EqualTo(42)); + } + + private static Utf8JsonReader CreateReader(string json) + { + var bytes = Encoding.UTF8.GetBytes(json); + + var reader = new Utf8JsonReader(bytes, new JsonReaderOptions + { + CommentHandling = JsonCommentHandling.Disallow, + AllowTrailingCommas = false + }); + + Assert.That(reader.Read(), Is.True, "Test JSON must produce at least one token."); + return reader; + } + } +} diff --git a/SysML2.NET.Serializer.Json/ModelInterchange/InterchangeChecksumDeserializer.cs b/SysML2.NET.Serializer.Json/ModelInterchange/InterchangeChecksumDeserializer.cs new file mode 100644 index 000000000..f6e29fa48 --- /dev/null +++ b/SysML2.NET.Serializer.Json/ModelInterchange/InterchangeChecksumDeserializer.cs @@ -0,0 +1,134 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Serializer.Json.ModelInterchange +{ + using System; + using System.Buffers; + using System.Text.Json; + + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Logging.Abstractions; + + using SysML2.NET.ModelInterchange; + using SysML2.NET.Serializer.Json.Utility; + + /// + /// Provides low-level JSON deserialization support for objects. + /// + public static class InterchangeChecksumDeserializer + { + /// + /// Deserializes a JSON object representing an + /// instance from the current position of a . + /// + /// + /// The positioned on a + /// token that begins an JSON object. + /// + /// + /// Optional used to create a logger for diagnostics. + /// + /// + /// A populated instance. + /// + /// + /// Thrown when the JSON structure does not conform to the expected object-based + /// representation of a checksum descriptor. + /// + /// + /// Recognized properties: + /// + /// + /// value — mandatory hexadecimal checksum value. + /// + /// + /// algorithm — mandatory checksum algorithm identifier (e.g. SHA256). + /// + /// + /// All other properties are ignored. + /// + public static InterchangeChecksum DeSerialize(ref Utf8JsonReader reader, ILoggerFactory loggerFactory = null) + { + var logger = loggerFactory == null ? NullLogger.Instance : loggerFactory.CreateLogger(nameof(InterchangeChecksum)); + + Utf8JsonReaderHelper.Expect(ref reader, JsonTokenType.StartObject); + + var checksum = new InterchangeChecksum(); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException("Expected property name in checksum object."); + } + + if (reader.ValueTextEquals("value"u8)) + { + reader.Read(); + checksum.Value = Utf8JsonReaderHelper.ReadStringOrNull(ref reader) ?? string.Empty; + continue; + } + + if (reader.ValueTextEquals("algorithm"u8)) + { + reader.Read(); + + if (reader.TokenType == JsonTokenType.String) + { + if (reader.HasValueSequence) + { + checksum.Algorithm = ChecksumKindProvider.Parse(reader.ValueSequence); + } + else + { + checksum.Algorithm = ChecksumKindProvider.Parse(reader.ValueSpan); + } + } + else if (reader.TokenType == JsonTokenType.Null) + { + // Leave default; tolerate missing algorithm for forward compatibility. + } + else + { + Utf8JsonReaderHelper.SkipValue(ref reader); + } + + continue; + } + + // Unknown property => skip value for forward compatibility + var propertyName = reader.GetString(); + + reader.Read(); + Utf8JsonReaderHelper.SkipValue(ref reader); + + logger.LogDebug("The property {Property} is unknown and skipped", propertyName); + } + + return checksum; + } + } +} diff --git a/SysML2.NET.Serializer.Json/ModelInterchange/InterchangeProjectDeSerializer.cs b/SysML2.NET.Serializer.Json/ModelInterchange/InterchangeProjectDeSerializer.cs new file mode 100644 index 000000000..e44eff335 --- /dev/null +++ b/SysML2.NET.Serializer.Json/ModelInterchange/InterchangeProjectDeSerializer.cs @@ -0,0 +1,233 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Serializer.Json.ModelInterchange +{ + using System.Collections.Generic; + using System.Text.Json; + + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Logging.Abstractions; + + using SysML2.NET.ModelInterchange; + using SysML2.NET.Serializer.Json.Utility; + + /// + /// Provides low-level JSON deserialization support for the + /// descriptor defined by the KerML + /// Project Archive (.kpar) specification. + /// + /// + /// The implementation is intentionally tolerant: + /// unknown properties are skipped to allow forward compatibility + /// with future versions of the interchange specification. + /// + public static class InterchangeProjectDeSerializer + { + /// + /// Deserializes a JSON object representing an + /// from the current position of + /// a . + /// + /// + /// The positioned on a + /// token that begins + /// an JSON object. + /// + /// + /// The used to setup logging + /// + /// + /// A instance of . + /// + /// + /// Thrown when the JSON structure does not conform to the expected + /// object-based representation of an interchange project. + /// + public static InterchangeProject DeSerialize(ref Utf8JsonReader reader, ILoggerFactory loggerFactory = null) + { + var logger = loggerFactory == null ? NullLogger.Instance : loggerFactory.CreateLogger("InterchangeProjectDeSerializer"); + + Utf8JsonReaderHelper.Expect(ref reader, JsonTokenType.StartObject); + + var project = new InterchangeProject(); + var usage = new List(); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException("Expected property name."); + } + + if (reader.ValueTextEquals("name"u8)) + { + reader.Read(); + project.Name = Utf8JsonReaderHelper.ReadStringOrNull(ref reader); + continue; + } + + if (reader.ValueTextEquals("description"u8)) + { + reader.Read(); + project.Description = Utf8JsonReaderHelper.ReadStringOrNull(ref reader); + continue; + } + + if (reader.ValueTextEquals("version"u8)) + { + reader.Read(); + project.Version = Utf8JsonReaderHelper.ReadStringOrNull(ref reader); + continue; + } + + if (reader.ValueTextEquals("license"u8)) + { + reader.Read(); + project.License = Utf8JsonReaderHelper.ReadStringOrNull(ref reader); + continue; + } + + if (reader.ValueTextEquals("maintainer"u8)) + { + reader.Read(); + project.Maintainer.Clear(); + + if (reader.TokenType == JsonTokenType.StartArray) + { + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + { + break; + } + + if (reader.TokenType == JsonTokenType.String) + { + var s = reader.GetString(); + if (s is not null) + { + project.Maintainer.Add(s); + } + } + else if (reader.TokenType != JsonTokenType.Null) + { + throw new JsonException("Expected string in maintainer array."); + } + } + } + else if (reader.TokenType != JsonTokenType.Null) + { + Utf8JsonReaderHelper.SkipValue(ref reader); + } + + continue; + } + + if (reader.ValueTextEquals("website"u8)) + { + reader.Read(); + project.Website = Utf8JsonReaderHelper.ReadStringOrNull(ref reader); + continue; + } + + if (reader.ValueTextEquals("topic"u8)) + { + reader.Read(); + project.Topic.Clear(); + + if (reader.TokenType == JsonTokenType.StartArray) + { + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + { + break; + } + + if (reader.TokenType == JsonTokenType.String) + { + var s = reader.GetString(); + if (s is not null) + { + project.Topic.Add(s); + } + } + else if (reader.TokenType != JsonTokenType.Null) + { + throw new JsonException("Expected string in topic array."); + } + } + } + else if (reader.TokenType != JsonTokenType.Null) + { + Utf8JsonReaderHelper.SkipValue(ref reader); + } + + continue; + } + + if (reader.ValueTextEquals("usage"u8)) + { + reader.Read(); + usage.Clear(); + + if (reader.TokenType == JsonTokenType.StartArray) + { + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + { + break; + } + + if (reader.TokenType == JsonTokenType.Null) + { + continue; + } + + var u = InterchangeProjectUsageDeSerializer.DeSerialize(ref reader, loggerFactory); + usage.Add(u); + } + } + else if (reader.TokenType != JsonTokenType.Null) + { + Utf8JsonReaderHelper.SkipValue(ref reader); + } + + project.Usage = usage; + continue; + } + + reader.Read(); + Utf8JsonReaderHelper.SkipValue(ref reader); + + logger.LogDebug("The property {Property} is unknown and skipped", reader.GetString()); + } + + return project; + } + } +} diff --git a/SysML2.NET.Serializer.Json/ModelInterchange/InterchangeProjectMetadataDeSerializer.cs b/SysML2.NET.Serializer.Json/ModelInterchange/InterchangeProjectMetadataDeSerializer.cs new file mode 100644 index 000000000..4f9c89ebd --- /dev/null +++ b/SysML2.NET.Serializer.Json/ModelInterchange/InterchangeProjectMetadataDeSerializer.cs @@ -0,0 +1,257 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Serializer.Json.ModelInterchange +{ + using System; + using System.Text.Json; + + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Logging.Abstractions; + + using SysML2.NET.ModelInterchange; + using SysML2.NET.Serializer.Json.Utility; + + /// + /// Provides low-level JSON deserialization support for the + /// descriptor stored in .meta.json, + /// as defined by the KerML Project Archive (.kpar) specification. + /// + /// + /// This deserializer operates directly on a forward-only + /// and does not rely on JSON annotations, reflection, or intermediate DOM representations. + /// + /// + /// The implementation is intentionally tolerant: unknown properties are skipped to allow + /// forward compatibility with future versions of the interchange specification. + /// + /// + public static class InterchangeProjectMetadataDeSerializer + { + /// + /// Deserializes a JSON object representing an + /// instance from the current position of a . + /// + /// + /// The positioned on a + /// token that begins an JSON object. + /// + /// + /// Optional used to create a logger for diagnostics. + /// + /// + /// A populated instance. + /// + /// + /// Thrown when the JSON structure does not conform to the expected object-based + /// representation of interchange project metadata. + /// + /// + /// The following properties are recognized when present: + /// + /// + /// + /// index — mandatory JSON object mapping names to archive-relative model paths. + /// + /// + /// + /// + /// created — mandatory ISO 8601 date-time string. + /// + /// + /// + /// + /// metamodel — optional IRI/URI identifying the metamodel (language). + /// + /// + /// + /// + /// includesDerived — optional boolean indicating whether derived property values are included. + /// + /// + /// + /// + /// includesImplied — optional boolean indicating whether implied relationships are included. + /// + /// + /// + /// + /// All other properties are ignored. + /// + public static InterchangeProjectMetadata DeSerialize(ref Utf8JsonReader reader, ILoggerFactory loggerFactory = null) + { + var logger = loggerFactory == null ? NullLogger.Instance : loggerFactory.CreateLogger(nameof(InterchangeProjectMetadataDeSerializer)); + + Utf8JsonReaderHelper.Expect(ref reader, JsonTokenType.StartObject); + + var metadata = new InterchangeProjectMetadata(); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException("Expected property name."); + } + + if (reader.ValueTextEquals("index"u8)) + { + reader.Read(); + metadata.Index.Clear(); + + if (reader.TokenType == JsonTokenType.StartObject) + { + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException("Expected property name in index object."); + } + + var key = reader.GetString(); + + reader.Read(); + var value = Utf8JsonReaderHelper.ReadStringOrNull(ref reader); + + if (key is not null && value is not null) + { + metadata.Index[key] = value; + } + } + } + else if (reader.TokenType != JsonTokenType.Null) + { + Utf8JsonReaderHelper.SkipValue(ref reader); + } + + continue; + } + + if (reader.ValueTextEquals("created"u8)) + { + reader.Read(); + metadata.Created = Utf8JsonReaderHelper.ReadDateTimeIso8601(ref reader); + continue; + } + + if (reader.ValueTextEquals("metamodel"u8)) + { + reader.Read(); + metadata.Metamodel = + Utf8JsonReaderHelper.ReadUriOrNull(ref reader); + continue; + } + + if (reader.ValueTextEquals("includesDerived"u8)) + { + reader.Read(); + var value = Utf8JsonReaderHelper.ReadBoolOrNull(ref reader); + if (value.HasValue) + { + metadata.IncludesDerived = value.Value; + } + + continue; + } + + if (reader.ValueTextEquals("includesImplied"u8)) + { + reader.Read(); + var value = Utf8JsonReaderHelper.ReadBoolOrNull(ref reader); + if (value.HasValue) + { + metadata.IncludesImplied = value.Value; + } + + continue; + } + + if (reader.ValueTextEquals("checksum"u8)) + { + reader.Read(); + metadata.Checksum.Clear(); + + if (reader.TokenType == JsonTokenType.StartObject) + { + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException("Expected property name in checksum object."); + } + + var path = reader.GetString(); + + reader.Read(); + + if (reader.TokenType == JsonTokenType.StartObject) + { + var checksum = InterchangeChecksumDeserializer.DeSerialize(ref reader, loggerFactory); + + if (path is not null) + { + metadata.Checksum[path] = checksum; + } + } + else if (reader.TokenType == JsonTokenType.Null) + { + // tolerate null checksum entry + } + else + { + Utf8JsonReaderHelper.SkipValue(ref reader); + } + } + } + else if (reader.TokenType != JsonTokenType.Null) + { + Utf8JsonReaderHelper.SkipValue(ref reader); + } + + continue; + } + + // Unknown property => skip value for forward compatibility + var propertyName = reader.GetString(); + + reader.Read(); + Utf8JsonReaderHelper.SkipValue(ref reader); + + logger.LogDebug("The property {Property} is unknown and skipped", propertyName); + } + + return metadata; + } + } +} diff --git a/SysML2.NET.Serializer.Json/ModelInterchange/InterchangeProjectUsageDeSerializer.cs b/SysML2.NET.Serializer.Json/ModelInterchange/InterchangeProjectUsageDeSerializer.cs new file mode 100644 index 000000000..594a77d40 --- /dev/null +++ b/SysML2.NET.Serializer.Json/ModelInterchange/InterchangeProjectUsageDeSerializer.cs @@ -0,0 +1,109 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Serializer.Json.ModelInterchange +{ + using System; + using System.Text.Json; + + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Logging.Abstractions; + + using SysML2.NET.ModelInterchange; + using SysML2.NET.Serializer.Json.Utility; + + /// + /// Provides low-level JSON deserialization support for the + /// descriptor as defined by the + /// KerML Project Archive (.kpar) specification. + /// + /// + /// The implementation is intentionally tolerant: + /// unknown properties are skipped to allow forward compatibility + /// with future versions of the interchange specification. + /// + public static class InterchangeProjectUsageDeSerializer + { + /// + /// Deserializes a JSON object representing an + /// from the current position + /// of a . + /// + /// + /// The positioned on a + /// token that begins an + /// JSON object. + /// + /// + /// The used to setup logging + /// + /// + /// A instance of . + /// + /// + /// Thrown when the JSON structure does not conform to the expected + /// object-based representation of an interchange project. + /// + public static InterchangeProjectUsage DeSerialize(ref Utf8JsonReader reader, ILoggerFactory loggerFactory = null) + { + var logger = loggerFactory == null ? NullLogger.Instance : loggerFactory.CreateLogger("InterchangeProjectUsageDeSerializer"); + + Utf8JsonReaderHelper.Expect(ref reader, JsonTokenType.StartObject); + + var usage = new InterchangeProjectUsage(); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException("Expected property name."); + } + + if (reader.ValueTextEquals("resource"u8)) + { + reader.Read(); + usage.Resource = + Utf8JsonReaderHelper.ReadUriOrNull(ref reader) + ?? new Uri("about:blank", UriKind.RelativeOrAbsolute); + continue; + } + + if (reader.ValueTextEquals("versionConstraint"u8)) + { + reader.Read(); + usage.VersionConstraint = Utf8JsonReaderHelper.ReadStringOrNull(ref reader); + continue; + } + + reader.Read(); + Utf8JsonReaderHelper.SkipValue(ref reader); + + logger.LogDebug("The property {Property} is unknown and skipped", reader.GetString()); + } + + return usage; + } + } +} diff --git a/SysML2.NET.Serializer.Json/Utility/Utf8JsonReaderHelper.cs b/SysML2.NET.Serializer.Json/Utility/Utf8JsonReaderHelper.cs new file mode 100644 index 000000000..096b9fdc1 --- /dev/null +++ b/SysML2.NET.Serializer.Json/Utility/Utf8JsonReaderHelper.cs @@ -0,0 +1,171 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Serializer.Json.Utility +{ + using System; + using System.Globalization; + using System.Text.Json; + + /// + /// Provides low-level, allocation-minimizing helper methods for working directly + /// with in streaming JSON deserializers. + /// + public static class Utf8JsonReaderHelper + { + /// + /// Ensures that the current token of the matches + /// the expected . + /// + /// + /// The positioned on the token to validate. + /// + /// + /// The expected . + /// + /// + /// Thrown when the current token does not match the expected token type. + /// + /// + /// This method is typically used immediately after advancing the reader + /// (for example, when entering an object or array) to fail fast on malformed JSON. + /// + public static void Expect(ref Utf8JsonReader reader, JsonTokenType tokenType) + { + if (reader.TokenType != tokenType) + { + throw new JsonException($"Expected {tokenType}, got {reader.TokenType}."); + } + } + + /// + /// Reads the current JSON value as a string or . + /// + /// + /// The positioned on the value token. + /// + /// + /// The string value if the token is , + /// or if the token is . + /// + /// + /// Thrown when the token is neither a string nor . + /// + /// + /// This helper avoids repeated token checks in generated deserializers + /// and enforces a strict string | null contract. + /// + public static string ReadStringOrNull(ref Utf8JsonReader reader) + { + if (reader.TokenType == JsonTokenType.Null) return null; + + if (reader.TokenType != JsonTokenType.String) throw new JsonException("Expected string or null."); + + return reader.GetString(); + } + + /// + /// Reads the current JSON value as a nullable boolean. + /// + /// + /// The positioned on the value token. + /// + /// + /// or when the token represents + /// a JSON boolean, or when the token is . + /// + /// + /// Thrown when the token is neither a boolean nor . + /// + /// + /// Intended for optional boolean properties where absence is semantically + /// different from an explicit false. + /// + public static bool? ReadBoolOrNull(ref Utf8JsonReader reader) + { + if (reader.TokenType == JsonTokenType.Null) return null; + + if (reader.TokenType == JsonTokenType.True) return true; + + if (reader.TokenType == JsonTokenType.False) return false; + + throw new JsonException("Expected bool or null."); + } + + /// + /// Reads the current JSON value as an ISO 8601 date-time string and parses it + /// into a using round-trip semantics. + /// + /// + /// The positioned on the value token. + /// + /// + /// A parsed using . + /// + /// + /// Thrown when the value is , empty, or not a valid ISO 8601 date-time string. + /// + public static DateTime ReadDateTimeIso8601(ref Utf8JsonReader reader) + { + var s = ReadStringOrNull(ref reader); + + if (string.IsNullOrWhiteSpace(s)) + { + throw new JsonException("Expected ISO 8601 date-time string."); + } + + return DateTime.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); + } + + /// + /// Reads the current JSON value as a or . + /// + /// + /// The positioned on the value token. + /// + /// + /// A created from the string value, or + /// if the token is or an empty string. + /// + /// + /// Thrown when the string value cannot be parsed as a URI. + /// + public static Uri ReadUriOrNull(ref Utf8JsonReader reader) + { + var s = ReadStringOrNull(ref reader); + + if (string.IsNullOrWhiteSpace(s)) return null; + + return new Uri(s, UriKind.RelativeOrAbsolute); + } + + /// + /// Skips the current JSON value, including any nested objects or arrays. + /// + /// + /// The positioned on the value token to skip. + /// + /// + /// This method is used to safely ignore unknown or unsupported properties + /// while remaining forward-compatible with newer schema versions. + /// + public static void SkipValue(ref Utf8JsonReader reader) => reader.Skip(); + } +} diff --git a/SysML2.NET.sln b/SysML2.NET.sln index d1814dac6..f97d22fe0 100644 --- a/SysML2.NET.sln +++ b/SysML2.NET.sln @@ -63,6 +63,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SysML2.NET.Serializer.Xmi", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SysML2.NET.Serializer.Xmi.Tests", "SysML2.NET.Serializer.Xmi.Tests\SysML2.NET.Serializer.Xmi.Tests.csproj", "{0E02F812-6B73-4CD9-A96F-54D7389873CF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SysML2.NET.Kpar", "SysML2.NET.Kpar\SysML2.NET.Kpar.csproj", "{D199B169-CEF0-4D6E-9CC5-83AB86EB809B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SysML2.NET.Kpar.Tests", "SysML2.NET.Kpar.Tests\SysML2.NET.Kpar.Tests.csproj", "{ACA8A0A2-EF4E-4F22-9164-07F6550F81F0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -137,6 +141,14 @@ Global {0E02F812-6B73-4CD9-A96F-54D7389873CF}.Debug|Any CPU.Build.0 = Debug|Any CPU {0E02F812-6B73-4CD9-A96F-54D7389873CF}.Release|Any CPU.ActiveCfg = Release|Any CPU {0E02F812-6B73-4CD9-A96F-54D7389873CF}.Release|Any CPU.Build.0 = Release|Any CPU + {D199B169-CEF0-4D6E-9CC5-83AB86EB809B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D199B169-CEF0-4D6E-9CC5-83AB86EB809B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D199B169-CEF0-4D6E-9CC5-83AB86EB809B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D199B169-CEF0-4D6E-9CC5-83AB86EB809B}.Release|Any CPU.Build.0 = Release|Any CPU + {ACA8A0A2-EF4E-4F22-9164-07F6550F81F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ACA8A0A2-EF4E-4F22-9164-07F6550F81F0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ACA8A0A2-EF4E-4F22-9164-07F6550F81F0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ACA8A0A2-EF4E-4F22-9164-07F6550F81F0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SysML2.NET/ModelInterchange/Archive.cs b/SysML2.NET/ModelInterchange/Archive.cs new file mode 100644 index 000000000..f1845e05e --- /dev/null +++ b/SysML2.NET/ModelInterchange/Archive.cs @@ -0,0 +1,59 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.ModelInterchange +{ + using System; + using System.Collections.Generic; + + /// + /// Represents the logical contents of a .kpar archive. + /// + public class Archive + { + /// + /// Gets or sets the interchange project descriptor (serialized to .project.json). + /// + public InterchangeProject Project { get; set; } + + /// + /// Gets or sets the interchange metadata (serialized to .meta.json). + /// + public InterchangeProjectMetadata Metadata { get; set; } + + /// + /// Gets the Path of the kpar that has been read + /// + public string Path { get; set; } + + /// + /// Gets the model interchange files contained in the archive. + /// + /// + /// Each entry uses an archive-relative path (forward slashes). + /// + public IReadOnlyList Models { get; set; } = Array.Empty(); + + /// + /// Gets the checksum validation mismatches detected while reading a .kpar archive. + /// + public IReadOnlyList ChecksumMismatches { get; set; } = Array.Empty(); + } +} diff --git a/SysML2.NET/ModelInterchange/ChecksumKind.cs b/SysML2.NET/ModelInterchange/ChecksumKind.cs new file mode 100644 index 000000000..1bdada140 --- /dev/null +++ b/SysML2.NET/ModelInterchange/ChecksumKind.cs @@ -0,0 +1,108 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2025 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.ModelInterchange +{ + /// + /// Identifies the algorithm used to compute a checksum value in an interchange project. + /// + public enum ChecksumKind + { + /// + /// SHA-1 hash algorithm. + /// + SHA1, + + /// + /// SHA-224 hash algorithm. + /// + SHA224, + + /// + /// SHA-256 hash algorithm. + /// + SHA256, + + /// + /// SHA-384 hash algorithm. + /// + SHA384, + + /// + /// SHA3-256 hash algorithm. + /// + SHA3256, + + /// + /// SHA3-384 hash algorithm. + /// + SHA3384, + + /// + /// SHA3-512 hash algorithm. + /// + SHA3512, + + /// + /// BLAKE2b-256 hash algorithm. + /// + BLAKE2b256, + + /// + /// BLAKE2b-384 hash algorithm. + /// + BLAKE2b384, + + /// + /// BLAKE2b-512 hash algorithm. + /// + BLAKE2b512, + + /// + /// BLAKE3 hash algorithm. + /// + BLAKE3, + + /// + /// MD2 hash algorithm. + /// + MD2, + + /// + /// MD4 hash algorithm. + /// + MD4, + + /// + /// MD5 hash algorithm. + /// + MD5, + + /// + /// MD6 hash algorithm. + /// + MD6, + + /// + /// ADLER32 checksum algorithm. + /// + ADLER32 + } +} diff --git a/SysML2.NET/ModelInterchange/ChecksumMismatch.cs b/SysML2.NET/ModelInterchange/ChecksumMismatch.cs new file mode 100644 index 000000000..c3ef8e91d --- /dev/null +++ b/SysML2.NET/ModelInterchange/ChecksumMismatch.cs @@ -0,0 +1,52 @@ +namespace SysML2.NET.ModelInterchange +{ + /// + /// Represents a checksum mismatch detected during validation of a .kpar archive. + /// + public sealed class ChecksumMismatch + { + /// + /// Gets the metadata index key associated with the model entry. + /// + /// + /// This corresponds to a key in the index object of .meta.json + /// (for example "Base"). + /// + public string IndexKey { get; set; } + + /// + /// Gets the archive-relative path of the model file. + /// + /// + /// This is the path within the ZIP container (for example "Base.kerml"), + /// normalized to use forward slashes. + /// + public string Path { get; set; } + + /// + /// Gets the checksum algorithm declared in .meta.json. + /// + /// + /// Typical values include SHA256, SHA3-256, or other + /// algorithms supported by the KerML specification. + /// + public ChecksumKind Algorithm { get; set; } + + /// + /// Gets the checksum value declared in .meta.json. + /// + /// + /// This value represents the expected integrity hash for the model file. + /// + public string Expected { get; set; } + + /// + /// Gets the checksum value computed from the actual file contents in the archive. + /// + /// + /// If this value differs from , the archive integrity + /// has been compromised or the archive contents have changed since creation. + /// + public string Actual { get; set; } + } +} diff --git a/SysML2.NET/ModelInterchange/InterchangeChecksum.cs b/SysML2.NET/ModelInterchange/InterchangeChecksum.cs new file mode 100644 index 000000000..06bb66839 --- /dev/null +++ b/SysML2.NET/ModelInterchange/InterchangeChecksum.cs @@ -0,0 +1,44 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.ModelInterchange +{ + /// + /// Represents a checksum entry for a model interchange file. + /// + public sealed class InterchangeChecksum + { + /// + /// Gets or sets the checksum value computed for the file. + /// + /// + /// Mandatory. Hexadecimal-encoded string. + /// + public string Value { get; set; } = string.Empty; + + /// + /// Gets or sets the algorithm used to compute the checksum. + /// + /// + /// Mandatory. Must match one of the supported algorithms defined by . + /// + public ChecksumKind Algorithm { get; set; } + } +} diff --git a/SysML2.NET/ModelInterchange/InterchangeProject.cs b/SysML2.NET/ModelInterchange/InterchangeProject.cs new file mode 100644 index 000000000..1fdb01702 --- /dev/null +++ b/SysML2.NET/ModelInterchange/InterchangeProject.cs @@ -0,0 +1,65 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.ModelInterchange +{ + using System.Collections.Generic; + + /// + /// KerML interchange project information stored in .project.json. + /// + /// + /// The .project.json file contains the InterchangeProject information (Figure 42), serialized as a single JSON object + /// according to the normative schema referenced by the specification. + /// + public class InterchangeProject : ProjectBase + { + /// + /// The version of the project being interchanged. + /// + public string Version { get; set; } + + /// + /// The license by which project content may be used. + /// + public string License { get; set; } + + /// + /// A list of names of maintainers of the project. + /// + public List Maintainer { get; set; } = []; + + /// + /// An IRI for a Web site with further information on the project. + /// + public string Website { get; set; } + + /// + /// A list of topics relevant to the project. + /// + public List Topic { get; set; } = []; + + /// + /// A list of project usage entries, one for each project used by the project + /// being interchanged, with properties as given below. + /// + public List Usage { get; set; } = []; + } +} diff --git a/SysML2.NET/ModelInterchange/InterchangeProjectMetadata.cs b/SysML2.NET/ModelInterchange/InterchangeProjectMetadata.cs new file mode 100644 index 000000000..f9a90c116 --- /dev/null +++ b/SysML2.NET/ModelInterchange/InterchangeProjectMetadata.cs @@ -0,0 +1,86 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.ModelInterchange +{ + using System; + using System.Collections.Generic; + + /// + /// KerML interchange project metadata stored in .meta.json. + /// + /// + /// The .meta.json file contains additional metadata for the project interchange archive, serialized as a single JSON object. + /// Table 13 in Section 10.3 lists the fields. + /// + public class InterchangeProjectMetadata + { + /// + /// Gets or sets the index of the global scope of the project. + /// + /// + /// Mandatory. A JSON object with a key for each name, mapping to the path of the model interchange file containing the + /// root namespace for the named element. + /// + public Dictionary Index { get; set; } = new(StringComparer.Ordinal); + + /// + /// Gets or sets the creation timestamp of the project interchange file. + /// + /// + /// Mandatory. Stored as an ISO 8601 date-time string in the serialized form. + /// + public DateTimeOffset Created { get; set; } + + /// + /// Gets or sets an optional IRI identifying the metamodel (language) of the models in the project interchange file. + /// + /// + /// Optional. For OMG-standardized languages, this is the version-specific URI specified by OMG; for KerML it has the form + /// https://www.omg.org/spec/KerML/yyyymmxx. If not given and the archive uses .kpar, the default is KerML. + /// + public Uri Metamodel { get; set; } + + /// + /// Gets or sets whether derived property values are included in the project’s XMI/JSON model interchange files. + /// + /// + /// Optional. If true, all derived properties must be included; if false, none may be included; if omitted, inclusion may vary. + /// + public bool IncludesDerived { get; set; } + + /// + /// Gets or sets whether implied relationships are included in the project’s XMI/JSON model interchange files. + /// + /// + /// Optional. If true, implied relationships must be included; if false, none may be included; if omitted, inclusion may vary. + /// + public bool IncludesImplied { get; set; } + + /// + /// Gets or sets the checksum dictionary for model interchange files. + /// + /// + /// Key = relative file path. + /// Value = checksum information for that file. + /// + public Dictionary Checksum { get; set; } = new(StringComparer.Ordinal); + } +} diff --git a/SysML2.NET/ModelInterchange/InterchangeProjectUsage.cs b/SysML2.NET/ModelInterchange/InterchangeProjectUsage.cs new file mode 100644 index 000000000..67744d72c --- /dev/null +++ b/SysML2.NET/ModelInterchange/InterchangeProjectUsage.cs @@ -0,0 +1,46 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.ModelInterchange +{ + using System; + + /// + /// Describes a used project entry in an usage list. + /// + public class InterchangeProjectUsage : ProjectUsageBase + { + /// + /// Gets or sets the IRI identifying the project being used. + /// + /// + /// Mandatory (within a usage). + /// + /// If the IRI is dereferenceable, it SHOULD resolve to a project interchange file + /// for the used project. + /// + public Uri Resource { get; set; } = new Uri("about:blank"); + + /// + /// A constraint on the allowable versions of a used project. + /// + public string VersionConstraint { get; set; } + } +} diff --git a/SysML2.NET/ModelInterchange/ModelEntry.cs b/SysML2.NET/ModelInterchange/ModelEntry.cs new file mode 100644 index 000000000..2b178b80a --- /dev/null +++ b/SysML2.NET/ModelInterchange/ModelEntry.cs @@ -0,0 +1,51 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.ModelInterchange +{ + using System; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + + /// + /// A model file entry inside a .kpar archive. + /// + public sealed class ModelEntry + { + /// + /// Gets the archive-relative path of the entry (e.g., structure/Body.json). + /// + public string Path { get; set; } + + /// + /// Gets an optional content type hint (e.g., application/json, application/xml, text/plain). + /// + public string ContentType { get; set; } + + /// + /// Gets a factory that opens a readable stream for the entry content. + /// + /// + /// Using a factory avoids lifetime issues when the package object outlives the underlying ZIP stream. + /// + public Func> OpenReadAsync { get; set; } + } +} diff --git a/SysML2.NET/ModelInterchange/ProjectBase.cs b/SysML2.NET/ModelInterchange/ProjectBase.cs new file mode 100644 index 0000000000000000000000000000000000000000..687cf142d1fda23b29586bde21436fd098623cc1 GIT binary patch literal 3562 zcmdUxT~AX%5QgWP-=N8cD@>pQCSC}V6vdCoR|F(@cPJGrP_U=9660T2pLe#~^Fa&T z8Pc5Yo;|a(Gw-}Jvw!~DQ+~k~9{g`+DFJl zWXI7}{Dl8G)+h$@F4&Mac~kgQ^`cR+<)~)Mr5&?7Lx&4>S zQS34M4!Rw5w~2VbN8zrMBYG|7$G#)*pJA806)!TZ(XP0!&uSQ9Gh|&z3#n5RH*!a) z&u-;Fdu&^@=>*9fdF?vJn*K=VBox>qL!4mIVY%2b-aSL*r_-wG6RpHbe*#22U0!O zW7LB0>S2ixPFXFiwfCfknDR`L$`8d|xJ&l#2o~NYp02{^lvzdnEh^(f?=v%C*9E5w z@K$WHQFYoQ^K#oFn^iftVc9<87FenhpTnW9XGRbCuO5X{)@SS&PpTv8#OlSXXc};W z>SzzWLa#29x|W!9?a?l{qZ4z^i5Zy~5%WlI%FwDEAv{7ysVj!ZoT$F2>})&tx{RtZ z(x9x64*474-V(kpdWMMK>Z+-aEV16OXGqPJ&`=&vw4>IWb9#(8R`*^kyBnibENqWZ zJVSmGj!kq|Jm-B1Mvj82Z>n4BBA@YOmr;>YWILXTF~5~fvCqiY*n5Pi@=IDS#_YJy znW_V-I*L;4tCM)By03nF6EjsSv9*tcxadW3~ zLHYy<`;*6?%>?+mMQ{ERImMh53Jei&) zb=m#V-7v9AaW3DBs!=JdaUgLSH=w*BEgO9M{`3E=#5sCl+ literal 0 HcmV?d00001 diff --git a/SysML2.NET/ModelInterchange/ProjectUsageBase.cs b/SysML2.NET/ModelInterchange/ProjectUsageBase.cs new file mode 100644 index 000000000..0250e75b8 --- /dev/null +++ b/SysML2.NET/ModelInterchange/ProjectUsageBase.cs @@ -0,0 +1,42 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.ModelInterchange +{ + /// + /// Base type for representing a “project usage” entry (a used project) in an interchange project. + /// + /// + /// Each usage entry identifies a used project via an IRI and may constrain which versions of that project are acceptable. + /// + public abstract class ProjectUsageBase + { + /// + /// TBD from Kerml Spec + /// + public ProjectBase UsingProject { get; set; } + + /// + /// TBD from Kerml Spec + /// + public ProjectBase UsedProject { get; set; } + } +} +