Compare commits

...

No commits in common. "main" and "realtime-0.7.10" have entirely different histories.

819 changed files with 255295 additions and 3 deletions

333
.gitignore vendored Normal file
View File

@ -0,0 +1,333 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
!Common/Common/Log/
Doc/

27
.travis.yml Normal file
View File

@ -0,0 +1,27 @@
language: csharp
solution: csharp-sdk.sln
install:
- nuget restore csharp-sdk.sln
addons:
apt:
update: true
before_deploy:
- bash ./script/package.sh
deploy:
provider: releases
api_key:
secure: U33UbFuAmwE/hsI3iYpUNLJ+3wkVBq5JxGd2Z03QU9jtYN+N1GCcBJ3oIsdyYZPDmwdqjcc/s2GGiy75NDljnaeI4/p+rfVw01e6Ht/CQPNioxqxmC3645YDsg8Iao0vrSP1aevurc/5Oq+DNk+s0DQn/sBK11ZxOO7dwxqBZqJSdYs6hXhenzC3qKMRw2Wu7Px/ETGbYSXlvVfmmMkw3CVutankT/QQPZM1u6uA8bJvcoPzOoUCuTMLfa+ie4O1WUYSwkyb+yYWjNkWhTo8b/scdVZjYmJ5tIIHP04AKump2kISBvXBysCdwScMvvZplJgVHc0x9qx+vvyGEWmKa3C4xDa5t0IwDHmApe6dPRc05WL9lwDh6KtiZ4vJEFvGfKPOXRmg4fDVnRIQHazMSFvFXgcwZoiPsMWnAvl45Cardbt1JLvlfGlnJ+wQ5RPev99LwvkXooJVqtEByR9AWozyGS8XFypbpFj2xpCe7ZJSmB8h0ElDsl2zmgWPeZkcOIFcVR2+2jl6B2XOnBWukxRUpeX1x+B4rDIKtTAHrkvtGNr5bb4Q6gUAQSqpvUDqV3gXdXW19H+yrlAImBFo6nS3qm/NzsVGmK+SaRzG6oOFfejW0NPnt2MdO0GV5s6L0CYp86Zpk/GrViO7AKjMqYfM2rwJS1rnLlxpTDoPcaA=
file:
- "LeanCloud-SDK-Storage-Standard.zip"
- "LeanCloud-SDK-Storage-Unity.zip"
- "LeanCloud-SDK-Realtime-Standard.zip"
- "LeanCloud-SDK-Realtime-Unity.zip"
- "LeanCloud-SDK-Engine-Standard.zip"
skip_cleanup: true
on:
repo: leancloud/csharp-sdk
tags: true
after_deploy:
- sudo apt-get install doxygen
- doxygen Doxyfile
- bash ./script/deploy.sh

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"python.pythonPath": "/Users/onerainyu/opt/anaconda3/bin/python"
}

View File

@ -0,0 +1,51 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<ReleaseVersion>0.7.10</ReleaseVersion>
<AssemblyName>Common</AssemblyName>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Libs\Newtonsoft.Json.AOT\LC.Newtonsoft.Json.AOT.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Common\LCCore.cs">
<Link>Common\LCCore.cs</Link>
</Compile>
<Compile Include="..\Common\AppRouter\LCAppServer.cs">
<Link>Common\AppRouter\LCAppServer.cs</Link>
</Compile>
<Compile Include="..\Common\AppRouter\LCAppRouter.cs">
<Link>Common\AppRouter\LCAppRouter.cs</Link>
</Compile>
<Compile Include="..\Common\Json\LCJsonConverter.cs">
<Link>Common\Json\LCJsonConverter.cs</Link>
</Compile>
<Compile Include="..\Common\Json\LCJsonUtils.cs">
<Link>Common\Json\LCJsonUtils.cs</Link>
</Compile>
<Compile Include="..\Common\Http\LCHttpUtils.cs">
<Link>Common\Http\LCHttpUtils.cs</Link>
</Compile>
<Compile Include="..\Common\Http\LCHttpClient.cs">
<Link>Common\Http\LCHttpClient.cs</Link>
</Compile>
<Compile Include="..\Common\Persistence\PersistenceController.cs">
<Link>Common\Persistence\PersistenceController.cs</Link>
</Compile>
<Compile Include="..\Common\Persistence\IPersistence.cs">
<Link>Common\Persistence\IPersistence.cs</Link>
</Compile>
<Compile Include="..\Common\Log\LCLogger.cs">
<Link>Common\Log\LCLogger.cs</Link>
</Compile>
<Compile Include="..\Common\Log\LCLogLevel.cs">
<Link>Common\Log\LCLogLevel.cs</Link>
</Compile>
<Compile Include="..\Common\Exception\LCException.cs">
<Link>Common\Exception\LCException.cs</Link>
</Compile>
</ItemGroup>
</Project>

View File

@ -0,0 +1,82 @@
using System;
using System.Threading.Tasks;
using System.Net.Http;
using System.Collections.Generic;
using LC.Newtonsoft.Json;
namespace LeanCloud.Common {
public class LCAppRouter {
private readonly string appId;
private readonly string server;
private LCAppServer appServer;
public LCAppRouter(string appId, string server) {
if (!IsInternalApp(appId) && string.IsNullOrEmpty(server)) {
// 国内节点必须配置自定义域名
throw new Exception("Please init with your server url.");
}
this.appId = appId;
this.server = server;
}
public async Task<string> GetApiServer() {
// 优先返回用户自定义域名
if (!string.IsNullOrEmpty(server)) {
return server;
}
LCAppServer appServ = await FetchAppServer();
return appServ.ApiServer;
}
public async Task<string> GetRealtimeServer() {
if (!string.IsNullOrEmpty(server)) {
return server;
}
LCAppServer appServ = await FetchAppServer();
return appServ.PushServer;
}
async Task<LCAppServer> FetchAppServer() {
// 判断节点地区
if (!IsInternalApp(appId)) {
// 国内节点必须配置自定义域名
throw new Exception("Please init with your server url.");
}
// 向 App Router 请求地址
if (appServer == null || !appServer.IsValid) {
try {
HttpRequestMessage request = new HttpRequestMessage {
RequestUri = new Uri($"https://app-router.com/2/route?appId={appId}"),
Method = HttpMethod.Get
};
HttpClient client = new HttpClient();
LCHttpUtils.PrintRequest(client, request);
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
request.Dispose();
string resultString = await response.Content.ReadAsStringAsync();
response.Dispose();
LCHttpUtils.PrintResponse(response, resultString);
Dictionary<string, object> data = JsonConvert.DeserializeObject<Dictionary<string, object>>(resultString);
appServer = new LCAppServer(data);
} catch (Exception e) {
LCLogger.Error(e);
// 拉取服务地址失败后,使用国际节点的默认服务地址
appServer = LCAppServer.GetInternalFallbackAppServer(appId);
}
}
return appServer;
}
private static bool IsInternalApp(string appId) {
if (appId.Length < 9) {
return false;
}
string suffix = appId.Substring(appId.Length - 9);
return suffix == "-MdYXbMMI";
}
}
}

View File

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
namespace LeanCloud.Common {
public class LCAppServer {
public string ApiServer {
get; private set;
}
public string EngineServer {
get; private set;
}
public string PushServer {
get; private set;
}
public string RTMServer {
get; private set;
}
public bool IsValid {
get {
return ttl != -1 || DateTime.Now < expiredAt;
}
}
private readonly DateTime expiredAt;
private readonly int ttl;
public LCAppServer(Dictionary<string, object> data) {
ApiServer = GetUrlWithScheme(data["api_server"] as string);
PushServer = GetUrlWithScheme(data["push_server"] as string);
EngineServer = GetUrlWithScheme(data["engine_server"] as string);
ttl = (int)(long)data["ttl"];
expiredAt = DateTime.Now.AddSeconds(ttl);
}
private static string GetUrlWithScheme(string url) {
return url.StartsWith("https://") ? url : $"https://{url}";
}
internal static LCAppServer GetInternalFallbackAppServer(string appId) {
string prefix = appId.Substring(0, 8).ToLower();
return new LCAppServer(new Dictionary<string, object> {
{ "api_server", $"https://{prefix}.api.lncldglobal.com" },
{ "push_server", $"https://{prefix}.engine.lncldglobal.com" },
{ "engine_server", $"https://{prefix}.push.lncldglobal.com" },
{ "ttl", -1 }
});
}
}
}

View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<ReleaseVersion>0.7.10</ReleaseVersion>
<AssemblyName>Common</AssemblyName>
</PropertyGroup>
<ItemGroup>
<Folder Include="Log\" />
<Folder Include="Http\" />
<Folder Include="Exception\" />
<Folder Include="Persistence\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Libs\Newtonsoft.Json\LC.Newtonsoft.Json.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,31 @@
using System;
namespace LeanCloud {
/// <summary>
/// LeanCloud Exceptions
/// </summary>
public class LCException : Exception {
/// <summary>
/// Error code
/// </summary>
public int Code {
get; set;
}
/// <summary>
/// Error message
/// </summary>
public new string Message {
get; set;
}
public LCException(int code, string message) {
Code = code;
Message = message;
}
public override string ToString() {
return $"{Code} - {Message}";
}
}
}

View File

@ -0,0 +1,201 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Security.Cryptography;
using LC.Newtonsoft.Json;
namespace LeanCloud.Common {
public class LCHttpClient {
private readonly string appId;
readonly string appKey;
private readonly string server;
private readonly string sdkVersion;
readonly string apiVersion;
readonly HttpClient client;
readonly MD5 md5;
private Dictionary<string, Func<Task<string>>> runtimeHeaderTasks = new Dictionary<string, Func<Task<string>>>();
private Dictionary<string, string> additionalHeaders = new Dictionary<string, string>();
public LCHttpClient(string appId, string appKey, string server, string sdkVersion, string apiVersion) {
this.appId = appId;
this.appKey = appKey;
this.server = server;
this.sdkVersion = sdkVersion;
this.apiVersion = apiVersion;
client = new HttpClient();
ProductHeaderValue product = new ProductHeaderValue("LeanCloud-CSharp-SDK", sdkVersion);
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(product));
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("X-LC-Id", appId);
md5 = MD5.Create();
}
public void AddRuntimeHeaderTask(string key, Func<Task<string>> task) {
if (string.IsNullOrEmpty(key)) {
return;
}
if (task == null) {
return;
}
runtimeHeaderTasks[key] = task;
}
public void AddAddtionalHeader(string key, string value) {
if (string.IsNullOrEmpty(key)) {
return;
}
if (string.IsNullOrEmpty(value)) {
return;
}
additionalHeaders[key] = value;
}
public Task<T> Get<T>(string path,
Dictionary<string, object> headers = null,
Dictionary<string, object> queryParams = null) {
return Request<T>(path, HttpMethod.Get, headers, null, queryParams);
}
public Task<T> Post<T>(string path,
Dictionary<string, object> headers = null,
object data = null,
Dictionary<string, object> queryParams = null) {
return Request<T>(path, HttpMethod.Post, headers, data, queryParams);
}
public Task<T> Put<T>(string path,
Dictionary<string, object> headers = null,
object data = null,
Dictionary<string, object> queryParams = null) {
return Request<T>(path, HttpMethod.Put, headers, data, queryParams);
}
public Task Delete(string path,
Dictionary<string, object> headers = null,
object data = null,
Dictionary<string, object> queryParams = null) {
return Request<Dictionary<string, object>>(path, HttpMethod.Delete, headers, data, queryParams);
}
async Task<T> Request<T>(string path,
HttpMethod method,
Dictionary<string, object> headers = null,
object data = null,
Dictionary<string, object> queryParams = null) {
string url = await BuildUrl(path, queryParams);
HttpRequestMessage request = new HttpRequestMessage {
RequestUri = new Uri(url),
Method = method,
};
await FillHeaders(request.Headers, headers);
string content = null;
if (data != null) {
content = JsonConvert.SerializeObject(data);
StringContent requestContent = new StringContent(content);
requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
request.Content = requestContent;
}
LCHttpUtils.PrintRequest(client, request, content);
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
request.Dispose();
string resultString = await response.Content.ReadAsStringAsync();
response.Dispose();
LCHttpUtils.PrintResponse(response, resultString);
if (response.IsSuccessStatusCode) {
T ret = JsonConvert.DeserializeObject<T>(resultString,
LCJsonConverter.Default);
return ret;
}
throw HandleErrorResponse(response.StatusCode, resultString);
}
LCException HandleErrorResponse(HttpStatusCode statusCode, string responseContent) {
int code = (int)statusCode;
string message = responseContent;
try {
// 尝试获取 LeanCloud 返回错误信息
Dictionary<string, object> error = JsonConvert.DeserializeObject<Dictionary<string, object>>(responseContent,
LCJsonConverter.Default);
code = (int)error["code"];
message = error["error"].ToString();
} catch (Exception e) {
LCLogger.Error(e);
}
return new LCException(code, message);
}
async Task<string> BuildUrl(string path, Dictionary<string, object> queryParams = null) {
string apiServer = await LCCore.AppRouter.GetApiServer();
string url = $"{apiServer}/{apiVersion}/{path}";
if (queryParams != null) {
IEnumerable<string> queryPairs = queryParams.Select(kv => $"{kv.Key}={kv.Value}");
string queries = string.Join("&", queryPairs);
url = $"{url}?{queries}";
}
return url;
}
async Task FillHeaders(HttpRequestHeaders headers, Dictionary<string, object> reqHeaders = null) {
// 额外 headers
if (reqHeaders != null) {
foreach (KeyValuePair<string, object> kv in reqHeaders) {
headers.Add(kv.Key, kv.Value.ToString());
}
}
if (LCCore.UseMasterKey && !string.IsNullOrEmpty(LCCore.MasterKey)) {
// Master Key
headers.Add("X-LC-Key", $"{LCCore.MasterKey},master");
} else {
// 签名
long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
string data = $"{timestamp}{appKey}";
string hash = GetMd5Hash(md5, data);
string sign = $"{hash},{timestamp}";
headers.Add("X-LC-Sign", sign);
}
if (additionalHeaders.Count > 0) {
foreach (KeyValuePair<string, string> kv in additionalHeaders) {
headers.Add(kv.Key, kv.Value);
}
}
// 服务额外 headers
foreach (KeyValuePair<string, Func<Task<string>>> kv in runtimeHeaderTasks) {
if (headers.Contains(kv.Key)) {
continue;
}
string value = await kv.Value.Invoke();
if (value == null) {
continue;
}
headers.Add(kv.Key, value);
}
}
static string GetMd5Hash(MD5 md5Hash, string input) {
byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input));
StringBuilder sb = new StringBuilder();
for (int i = 0; i < data.Length; i++) {
sb.Append(data[i].ToString("x2"));
}
return sb.ToString();
}
}
}

View File

@ -0,0 +1,55 @@
using System.Linq;
using System.Text;
using System.Net.Http;
namespace LeanCloud.Common {
public static class LCHttpUtils {
public static void PrintRequest(HttpClient client, HttpRequestMessage request, string content = null) {
if (LCLogger.LogDelegate == null) {
return;
}
if (client == null) {
return;
}
if (request == null) {
return;
}
StringBuilder sb = new StringBuilder();
sb.AppendLine("=== HTTP Request Start ===");
sb.AppendLine($"URL: {request.RequestUri}");
sb.AppendLine($"Method: {request.Method}");
sb.AppendLine($"Headers: ");
foreach (var header in client.DefaultRequestHeaders) {
sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}");
}
foreach (var header in request.Headers) {
sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}");
}
if (request.Content != null) {
foreach (var header in request.Content.Headers) {
sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}");
}
}
if (!string.IsNullOrEmpty(content)) {
sb.AppendLine($"Content: {content}");
}
sb.AppendLine("=== HTTP Request End ===");
LCLogger.Debug(sb.ToString());
}
public static void PrintResponse(HttpResponseMessage response, string content = null) {
if (LCLogger.LogDelegate == null) {
return;
}
StringBuilder sb = new StringBuilder();
sb.AppendLine("=== HTTP Response Start ===");
sb.AppendLine($"URL: {response.RequestMessage.RequestUri}");
sb.AppendLine($"Status Code: {response.StatusCode}");
if (!string.IsNullOrEmpty(content)) {
sb.AppendLine($"Content: {content}");
}
sb.AppendLine("=== HTTP Response End ===");
LCLogger.Debug(sb.ToString());
}
}
}

View File

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using LC.Newtonsoft.Json;
namespace LeanCloud.Common {
public class LCJsonConverter : JsonConverter {
public override bool CanConvert(Type objectType) {
return objectType == typeof(object);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
serializer.Serialize(writer, value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
if (reader.TokenType == JsonToken.StartObject) {
var obj = new Dictionary<string, object>();
serializer.Populate(reader, obj);
return obj;
}
if (reader.TokenType == JsonToken.StartArray) {
var arr = new List<object>();
serializer.Populate(reader, arr);
return arr;
}
if (reader.TokenType == JsonToken.Integer) {
if ((long)reader.Value < int.MaxValue) {
return Convert.ToInt32(reader.Value);
}
}
if (reader.TokenType == JsonToken.Float) {
return Convert.ToSingle(reader.Value);
}
return serializer.Deserialize(reader);
}
public readonly static LCJsonConverter Default = new LCJsonConverter();
}
}

View File

@ -0,0 +1,37 @@
using LC.Newtonsoft.Json;
using System.Threading.Tasks;
namespace LeanCloud.Common {
/// <summary>
/// Serialize and deserialize
/// </summary>
public static class LCJsonUtils {
public static async Task<string> SerializeObjectAsync(object obj) {
string str = null;
await Task.Run(() => {
str = JsonConvert.SerializeObject(obj);
});
return str;
}
public static Task<string> SerializeAsync(object obj) {
return Task.Run(() => {
return JsonConvert.SerializeObject(obj);
});
}
public static async Task<T> DeserializeObjectAsync<T>(string str, params JsonConverter[] converters) {
T obj = default;
await Task.Run(() => {
obj = JsonConvert.DeserializeObject<T>(str, converters);
});
return obj;
}
public static Task<T> DeserializeAsync<T>(string str, params JsonConverter[] converts) {
return Task.Run(() => {
return JsonConvert.DeserializeObject<T>(str, converts);
});
}
}
}

67
Common/Common/LCCore.cs Normal file
View File

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
namespace LeanCloud.Common {
/// <summary>
/// LeanCloud Application
/// </summary>
public class LCCore {
// SDK 版本号,用于 User-Agent 统计
public const string SDKVersion = "0.7.10";
// 接口版本号,用于接口版本管理
public const string APIVersion = "1.1";
public static string AppId {
get; private set;
}
public static string AppKey {
get; private set;
}
public static string MasterKey {
get; private set;
}
public static bool UseProduction {
get; set;
}
public static LCAppRouter AppRouter {
get; private set;
}
public static LCHttpClient HttpClient {
get; private set;
}
public static bool UseMasterKey {
get; set;
}
public static PersistenceController PersistenceController {
get; set;
}
public static void Initialize(string appId,
string appKey,
string server = null,
string masterKey = null) {
if (string.IsNullOrEmpty(appId)) {
throw new ArgumentException(nameof(appId));
}
if (string.IsNullOrEmpty(appKey)) {
throw new ArgumentException(nameof(appKey));
}
AppId = appId;
AppKey = appKey;
MasterKey = masterKey;
AppRouter = new LCAppRouter(appId, server);
HttpClient = new LCHttpClient(appId, appKey, server, SDKVersion, APIVersion);
}
}
}

View File

@ -0,0 +1,7 @@
namespace LeanCloud {
public enum LCLogLevel {
Debug,
Warn,
Error,
}
}

View File

@ -0,0 +1,51 @@
using System;
using System.Text;
namespace LeanCloud {
/// <summary>
/// Logger
/// </summary>
public static class LCLogger {
/// <summary>
/// Configures the logger.
/// </summary>
/// <value>The log delegate.</value>
public static Action<LCLogLevel, string> LogDelegate {
get; set;
}
public static void Debug(string log) {
LogDelegate?.Invoke(LCLogLevel.Debug, log);
}
public static void Debug(string format, params object[] args) {
LogDelegate?.Invoke(LCLogLevel.Debug, string.Format(format, args));
}
public static void Warn(string log) {
LogDelegate?.Invoke(LCLogLevel.Warn, log);
}
public static void Warn(string format, params object[] args) {
LogDelegate?.Invoke(LCLogLevel.Warn, string.Format(format, args));
}
public static void Error(string log) {
LogDelegate?.Invoke(LCLogLevel.Error, log);
}
public static void Error(string format, params object[] args) {
LogDelegate?.Invoke(LCLogLevel.Error, string.Format(format, args));
}
public static void Error(Exception e) {
StringBuilder sb = new StringBuilder();
sb.Append(e.GetType());
sb.Append("\n");
sb.Append(e.Message);
sb.Append("\n");
sb.Append(e.StackTrace);
Error(sb.ToString());
}
}
}

View File

@ -0,0 +1,5 @@
namespace LeanCloud.Common {
public interface IPersistence {
string GetPersistencePath();
}
}

View File

@ -0,0 +1,64 @@
using System;
using System.Threading.Tasks;
using System.IO;
using System.Text;
using IOFile = System.IO.File;
namespace LeanCloud.Common {
public class PersistenceController {
private readonly IPersistence persistence;
public PersistenceController(IPersistence persistence) {
this.persistence = persistence;
}
public async Task WriteText(string filename, string text) {
if (persistence == null) {
return;
}
string path = GetFileFullPath(filename);
using (FileStream fs = IOFile.Create(path)) {
byte[] buffer = Encoding.UTF8.GetBytes(text);
await fs.WriteAsync(buffer, 0, buffer.Length);
}
}
public async Task<string> ReadText(string filename) {
if (persistence == null) {
return null;
}
string path = GetFileFullPath(filename);
if (IOFile.Exists(path)) {
string text;
using (FileStream fs = IOFile.OpenRead(path)) {
byte[] buffer = new byte[fs.Length];
await fs.ReadAsync(buffer, 0, (int)fs.Length);
text = Encoding.UTF8.GetString(buffer);
}
return text;
}
return null;
}
public Task Delete(string filename) {
if (persistence == null) {
return Task.CompletedTask;
}
string path = GetFileFullPath(filename);
return Task.Run(() => {
IOFile.Delete(path);
});
}
private string GetFileFullPath(string filename) {
if (persistence == null) {
throw new Exception("no IStrorage.");
}
return Path.Combine(persistence.GetPersistencePath(), filename);
}
}
}

19
Doxyfile Normal file
View File

@ -0,0 +1,19 @@
DOXYFILE_ENCODING = UTF-8
PROJECT_NAME = "LeanCloud C# SDK"
OUTPUT_DIRECTORY = ./Doc/
EXTRACT_ALL = yes
EXTRACT_PRIVATE = no
EXTRACT_STATIC = yes
INPUT = ./Storage/Storage/Public ./Storage/Storage.Unity/Public ./Realtime/Realtime/Public ./LiveQuery/LiveQuery/Public ./Engine/Public
#Do not add anything here unless you need to. Doxygen already covers all
#common formats like .c/.cc/.cxx/.c++/.cpp/.inl/.h/.hpp
FILE_PATTERNS =
RECURSIVE = yes
#for HTML jj
GENERATE_HTML = YES
HTML_FILE_EXTENSION = .html
GENERATE_LATEX = NO

22
Engine/Engine.csproj Normal file
View File

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<ReleaseVersion>0.7.10</ReleaseVersion>
<RootNamespace>LeanCloud.Engine</RootNamespace>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Storage\Storage.Standard\Storage.Standard.csproj" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<Folder Include="Public\" />
<Folder Include="Public\Attributes\" />
<Folder Include="Internal\" />
<Folder Include="Internal\Controllers\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,82 @@
using System;
using System.Text.Json;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Cors;
using LeanCloud.Storage.Internal.Object;
using LeanCloud.Storage;
namespace LeanCloud.Engine {
[ApiController]
[Route("{1,1.1}")]
[EnableCors(LCEngine.LCEngineCORS)]
public class LCClassHookController : ControllerBase {
private Dictionary<string, MethodInfo> ClassHooks => LCEngine.ClassHooks;
[HttpPost("functions/{className}/{hookName}")]
public async Task<object> Hook(string className, string hookName, JsonElement body) {
try {
LCLogger.Debug($"Hook: {className}#{hookName}");
LCLogger.Debug(body.ToString());
LCEngine.CheckHookKey(Request);
string classHookName = GetClassHookName(className, hookName);
if (ClassHooks.TryGetValue(classHookName, out MethodInfo mi)) {
Dictionary<string, object> data = LCEngine.Decode(body);
LCObjectData objectData = LCObjectData.Decode(data["object"] as Dictionary<string, object>);
objectData.ClassName = className;
LCObject obj = LCObject.Create(className);
obj.Merge(objectData);
// 避免死循环
if (hookName.StartsWith("before")) {
obj.DisableBeforeHook();
} else {
obj.DisableAfterHook();
}
LCEngine.InitRequestContext(Request);
LCUser user = null;
if (data.TryGetValue("user", out object userObj) &&
userObj != null) {
user = new LCUser();
user.Merge(LCObjectData.Decode(userObj as Dictionary<string, object>));
LCEngineRequestContext.CurrentUser = user;
}
LCObject result = await LCEngine.Invoke(mi, new object[] { obj }) as LCObject;
if (result != null) {
return LCCloud.Encode(result);
}
}
return body;
} catch (Exception e) {
return StatusCode(500, e.Message);
}
}
private static string GetClassHookName(string className, string hookName) {
switch (hookName) {
case "beforeSave":
return $"__before_save_for_{className}";
case "afterSave":
return $"__after_save_for_{className}";
case "beforeUpdate":
return $"__before_update_for_{className}";
case "afterUpdate":
return $"__after_update_for_{className}";
case "beforeDelete":
return $"__before_delete_for_{className}";
case "afterDelete":
return $"__after_delete_for_{className}";
default:
throw new Exception($"Error hook name: {hookName}");
}
}
}
}

View File

@ -0,0 +1,99 @@
using System;
using System.Text.Json;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Cors;
using LeanCloud.Storage;
using LeanCloud.Storage.Internal.Codec;
namespace LeanCloud.Engine {
[ApiController]
[Route("{1,1.1}")]
[EnableCors(LCEngine.LCEngineCORS)]
public class LCFunctionController : ControllerBase {
private Dictionary<string, MethodInfo> Functions => LCEngine.Functions;
[HttpGet("functions/_ops/metadatas")]
public object GetFunctions() {
try {
return LCEngine.GetFunctions(Request);
} catch (Exception e) {
return StatusCode(500, e.Message);
}
}
[HttpPost("functions/{funcName}")]
public async Task<object> Run(string funcName, JsonElement body) {
try {
LCLogger.Debug($"Run: {funcName}");
LCLogger.Debug(body.ToString());
if (Functions.TryGetValue(funcName, out MethodInfo mi)) {
LCEngine.InitRequestContext(Request);
object[] ps = ParseParameters(mi, body);
object result = await LCEngine.Invoke(mi, ps.ToArray());
if (result != null) {
return new Dictionary<string, object> {
{ "result", result }
};
}
}
return body;
} catch (Exception e) {
return StatusCode(500, e.Message);
}
}
[HttpPost("call/{funcName}")]
public async Task<object> RPC(string funcName, JsonElement body) {
try {
LCLogger.Debug($"RPC: {funcName}");
LCLogger.Debug(body.ToString());
if (Functions.TryGetValue(funcName, out MethodInfo mi)) {
LCEngine.InitRequestContext(Request);
object[] ps = ParseParameters(mi, body);
object result = await LCEngine.Invoke(mi, ps);
if (result != null) {
return new Dictionary<string, object> {
{ "result", LCCloud.Encode(result) }
};
}
}
return body;
} catch (Exception e) {
return StatusCode(500, e.Message);
}
}
private static object[] ParseParameters(MethodInfo mi, JsonElement body) {
Dictionary<string, object> parameters = LCEngine.Decode(body);
List<object> ps = new List<object>();
if (mi.GetParameters().Length > 0) {
if (Array.Exists(mi.GetParameters(),
p => p.GetCustomAttribute<LCEngineFunctionParamAttribute>() != null)) {
// 如果包含 LCEngineFunctionParamAttribute 的参数,则按照配对方式传递参数
foreach (ParameterInfo pi in mi.GetParameters()) {
LCEngineFunctionParamAttribute attr = pi.GetCustomAttribute<LCEngineFunctionParamAttribute>();
if (attr != null) {
string paramName = attr.ParamName;
ps.Add(parameters[paramName]);
}
}
} else {
ps.Add(LCDecoder.Decode(LCEngine.Decode(body)));
}
}
return ps.ToArray();
}
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Cors;
using LeanCloud.Common;
namespace LeanCloud.Engine {
[ApiController]
[Route("__engine/{1,1.1}")]
[EnableCors(LCEngine.LCEngineCORS)]
public class LCPingController : ControllerBase {
[HttpGet("ping")]
public object Get() {
LCLogger.Debug("Ping ~~~");
return new Dictionary<string, string> {
{ "runtime", $"dotnet-{Environment.Version}" },
{ "version", LCCore.SDKVersion }
};
}
}
}

View File

@ -0,0 +1,88 @@
using System;
using System.Text.Json;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Cors;
using LeanCloud.Storage.Internal.Object;
using LeanCloud.Storage;
namespace LeanCloud.Engine {
[ApiController]
[Route("{1,1.1}/functions")]
[EnableCors(LCEngine.LCEngineCORS)]
public class LCUserHookController : ControllerBase {
private Dictionary<string, MethodInfo> UserHooks => LCEngine.UserHooks;
[HttpPost("onVerified/sms")]
public async Task<object> HookSMSVerification(JsonElement body) {
try {
LCLogger.Debug(LCEngine.OnSMSVerified);
LCLogger.Debug(body.ToString());
LCEngine.CheckHookKey(Request);
if (UserHooks.TryGetValue(LCEngine.OnSMSVerified, out MethodInfo mi)) {
LCEngine.InitRequestContext(Request);
Dictionary<string, object> dict = LCEngine.Decode(body);
return await Invoke(mi, dict);
}
return body;
} catch (Exception e) {
return StatusCode(500, e.Message);
}
}
[HttpPost("onVerified/email")]
public async Task<object> HookEmailVerification(JsonElement body) {
try {
LCLogger.Debug(LCEngine.OnEmailVerified);
LCLogger.Debug(body.ToString());
LCEngine.CheckHookKey(Request);
if (UserHooks.TryGetValue(LCEngine.OnEmailVerified, out MethodInfo mi)) {
LCEngine.InitRequestContext(Request);
Dictionary<string, object> dict = LCEngine.Decode(body);
return await Invoke(mi, dict);
}
return body;
} catch (Exception e) {
return StatusCode(500, e.Message);
}
}
[HttpPost("_User/onLogin")]
public async Task<object> HookLogin(JsonElement body) {
try {
LCLogger.Debug(LCEngine.OnLogin);
LCLogger.Debug(body.ToString());
LCEngine.CheckHookKey(Request);
if (UserHooks.TryGetValue(LCEngine.OnLogin, out MethodInfo mi)) {
LCEngine.InitRequestContext(Request);
Dictionary<string, object> dict = LCEngine.Decode(body);
return await Invoke(mi, dict);
}
return body;
} catch (Exception e) {
return StatusCode(500, e.Message);
}
}
private static async Task<object> Invoke(MethodInfo mi, Dictionary<string, object> dict) {
LCObjectData objectData = LCObjectData.Decode(dict["object"] as Dictionary<string, object>);
objectData.ClassName = "_User";
LCObject user = LCObject.Create("_User");
user.Merge(objectData);
return await LCEngine.Invoke(mi, new object[] { user }) as LCObject;
}
}
}

View File

@ -0,0 +1,34 @@
using System;
namespace LeanCloud.Engine {
public enum LCEngineObjectHookType {
BeforeSave,
AfterSave,
BeforeUpdate,
AfterUpdate,
BeforeDelete,
AfterDelete
}
/// <summary>
/// LCEngineClassHookAttribute is an attribute that hooks class in LeanEngine.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class LCEngineClassHookAttribute : Attribute {
public string ClassName {
get;
}
public LCEngineObjectHookType HookType {
get;
}
public LCEngineClassHookAttribute(string className, LCEngineObjectHookType hookType) {
if (string.IsNullOrEmpty(className)) {
throw new ArgumentNullException(nameof(className));
}
ClassName = className;
HookType = hookType;
}
}
}

View File

@ -0,0 +1,20 @@
using System;
namespace LeanCloud.Engine {
/// <summary>
/// LCEngineFunctionAttribute is an attribute of cloud function in engine.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class LCEngineFunctionAttribute : Attribute {
public string FunctionName {
get;
}
public LCEngineFunctionAttribute(string funcName) {
if (string.IsNullOrEmpty(funcName)) {
throw new ArgumentNullException(nameof(funcName));
}
FunctionName = funcName;
}
}
}

View File

@ -0,0 +1,20 @@
using System;
namespace LeanCloud.Engine {
/// <summary>
/// LCEngineFunctionParamAttribute is an attribute of the parameter of cloud function in engine.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
public class LCEngineFunctionParamAttribute : Attribute {
public string ParamName {
get;
}
public LCEngineFunctionParamAttribute(string paramName) {
if (string.IsNullOrEmpty(paramName)) {
throw new ArgumentNullException(nameof(paramName));
}
ParamName = paramName;
}
}
}

View File

@ -0,0 +1,36 @@
using System;
namespace LeanCloud.Engine {
public enum LCEngineRealtimeHookType {
// 消息
MessageReceived,
MessageSent,
MessageUpdate,
ReceiversOffline,
// 对话
ConversationStart,
ConversationStarted,
ConversationAdd,
ConversationAdded,
ConversationRemove,
ConversationRemoved,
ConversationUpdate,
// 客户端
ClientOnline,
ClientOffline,
}
/// <summary>
/// LCEngineRealtimeHookAttribute is an attribute that hooks realtime in engine.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class LCEngineRealtimeHookAttribute : Attribute {
public LCEngineRealtimeHookType HookType {
get;
}
public LCEngineRealtimeHookAttribute(LCEngineRealtimeHookType hookType) {
HookType = hookType;
}
}
}

View File

@ -0,0 +1,23 @@
using System;
namespace LeanCloud.Engine {
public enum LCEngineUserHookType {
OnSMSVerified,
OnEmailVerified,
OnLogin
}
/// <summary>
/// LCEngineUserHookAttribute is an attribute that hooks user in engine.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class LCEngineUserHookAttribute : Attribute {
public LCEngineUserHookType HookType {
get;
}
public LCEngineUserHookAttribute(LCEngineUserHookType hookType) {
HookType = hookType;
}
}
}

320
Engine/Public/LCEngine.cs Normal file
View File

@ -0,0 +1,320 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.Threading.Tasks;
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using LC.Newtonsoft.Json;
using Microsoft.Extensions.Primitives;
using Microsoft.Extensions.DependencyInjection;
using LeanCloud.Common;
namespace LeanCloud.Engine {
/// <summary>
/// LCEngine provides the initialization of LeanEngine.
/// </summary>
public class LCEngine {
public const string LCEngineCORS = "LCEngineCORS";
const string LCMasterKeyName = "x-avoscloud-master-key";
const string LCHookKeyName = "x-lc-hook-key";
const string BeforeSave = "__before_save_for_";
const string AfterSave = "__after_save_for_";
const string BeforeUpdate = "__before_update_for_";
const string AfterUpdate = "__after_update_for_";
const string BeforeDelete = "__before_delete_for_";
const string AfterDelete = "__after_delete_for_";
internal const string OnSMSVerified = "__on_verified_sms";
internal const string OnEmailVerified = "__on_verified_email";
internal const string OnLogin = "__on_login__User";
const string ClientOnline = "_clientOnline";
const string ClientOffline = "_clientOffline";
const string MessageSent = "_messageSent";
const string MessageReceived = "_messageReceived";
const string ReceiversOffline = "_receiversOffline";
const string MessageUpdate = "_messageUpdate";
const string ConversationStart = "_conversationStart";
const string ConversationStarted = "_conversationStarted";
const string ConversationAdd = "_conversationAdd";
const string ConversationAdded = "_conversationAdded";
const string ConversationRemove = "_conversationRemove";
const string ConversationRemoved = "_conversationRemoved";
const string ConversationUpdate = "_conversationUpdate";
static readonly string[] LCEngineCORSMethods = new string[] {
"PUT",
"GET",
"POST",
"DELETE",
"OPTIONS"
};
static readonly string[] LCEngineCORSHeaders = new string[] {
"Content-Type",
"X-AVOSCloud-Application-Id",
"X-AVOSCloud-Application-Key",
"X-AVOSCloud-Application-Production",
"X-AVOSCloud-Client-Version",
"X-AVOSCloud-Request-Sign",
"X-AVOSCloud-Session-Token",
"X-AVOSCloud-Super-Key",
"X-LC-Hook-Key",
"X-LC-Id",
"X-LC-Key",
"X-LC-Prod",
"X-LC-Session",
"X-LC-Sign",
"X-LC-UA",
"X-Requested-With",
"X-Uluru-Application-Id",
"X-Uluru-Application-Key",
"X-Uluru-Application-Production",
"X-Uluru-Client-Version",
"X-Uluru-Session-Token"
};
public static Dictionary<string, MethodInfo> Functions = new Dictionary<string, MethodInfo>();
public static Dictionary<string, MethodInfo> ClassHooks = new Dictionary<string, MethodInfo>();
public static Dictionary<string, MethodInfo> UserHooks = new Dictionary<string, MethodInfo>();
/// <summary>
/// Initializes the engine with the given services.
/// </summary>
/// <param name="services"></param>
public static void Initialize(IServiceCollection services) {
// 获取环境变量
LCLogger.Debug("-------------------------------------------------");
PrintEnvironmentVar("LEANCLOUD_APP_ID");
PrintEnvironmentVar("LEANCLOUD_APP_KEY");
PrintEnvironmentVar("LEANCLOUD_APP_MASTER_KEY");
PrintEnvironmentVar("LEANCLOUD_APP_HOOK_KEY");
PrintEnvironmentVar("LEANCLOUD_API_SERVER");
PrintEnvironmentVar("LEANCLOUD_APP_PROD");
PrintEnvironmentVar("LEANCLOUD_APP_ENV");
PrintEnvironmentVar("LEANCLOUD_APP_INSTANCE");
PrintEnvironmentVar("LEANCLOUD_REGION");
PrintEnvironmentVar("LEANCLOUD_APP_ID");
PrintEnvironmentVar("LEANCLOUD_APP_DOMAIN");
PrintEnvironmentVar("LEANCLOUD_APP_PORT");
LCLogger.Debug("-------------------------------------------------");
LCApplication.Initialize(Environment.GetEnvironmentVariable("LEANCLOUD_APP_ID"),
Environment.GetEnvironmentVariable("LEANCLOUD_APP_KEY"),
Environment.GetEnvironmentVariable("LEANCLOUD_API_SERVER"),
Environment.GetEnvironmentVariable("LEANCLOUD_APP_MASTER_KEY"));
LCCore.HttpClient.AddAddtionalHeader(LCHookKeyName, Environment.GetEnvironmentVariable("LEANCLOUD_APP_HOOK_KEY"));
Assembly assembly = Assembly.GetCallingAssembly();
ClassHooks = assembly.GetTypes()
.SelectMany(t => t.GetMethods())
.Where(m => m.GetCustomAttribute<LCEngineClassHookAttribute>() != null)
.ToDictionary(mi => {
LCEngineClassHookAttribute attr = mi.GetCustomAttribute<LCEngineClassHookAttribute>();
switch (attr.HookType) {
case LCEngineObjectHookType.BeforeSave:
return $"{BeforeSave}{attr.ClassName}";
case LCEngineObjectHookType.AfterSave:
return $"{AfterSave}{attr.ClassName}";
case LCEngineObjectHookType.BeforeUpdate:
return $"{BeforeUpdate}{attr.ClassName}";
case LCEngineObjectHookType.AfterUpdate:
return $"{AfterUpdate}{attr.ClassName}";
case LCEngineObjectHookType.BeforeDelete:
return $"{BeforeDelete}{attr.ClassName}";
case LCEngineObjectHookType.AfterDelete:
return $"{AfterDelete}{attr.ClassName}";
default:
throw new Exception($"Error hook type: {attr.HookType}");
}
});
UserHooks = assembly.GetTypes()
.SelectMany(t => t.GetMethods())
.Where(m => m.GetCustomAttribute<LCEngineUserHookAttribute>() != null)
.ToDictionary(mi => {
LCEngineUserHookAttribute attr = mi.GetCustomAttribute<LCEngineUserHookAttribute>();
switch (attr.HookType) {
case LCEngineUserHookType.OnSMSVerified:
return OnSMSVerified;
case LCEngineUserHookType.OnEmailVerified:
return OnEmailVerified;
case LCEngineUserHookType.OnLogin:
return OnLogin;
default:
throw new Exception($"Error hook type: {attr.HookType}");
}
});
Functions = assembly.GetTypes()
.SelectMany(t => t.GetMethods())
.Where(m => m.GetCustomAttribute<LCEngineFunctionAttribute>() != null)
.ToDictionary(mi => mi.GetCustomAttribute<LCEngineFunctionAttribute>().FunctionName);
assembly.GetTypes()
.SelectMany(t => t.GetMethods())
.Where(m => m.GetCustomAttribute<LCEngineRealtimeHookAttribute>() != null)
.ToDictionary(mi => {
LCEngineRealtimeHookAttribute attr = mi.GetCustomAttribute<LCEngineRealtimeHookAttribute>();
switch (attr.HookType) {
case LCEngineRealtimeHookType.ClientOnline:
return ClientOnline;
case LCEngineRealtimeHookType.ClientOffline:
return ClientOffline;
case LCEngineRealtimeHookType.MessageSent:
return MessageSent;
case LCEngineRealtimeHookType.MessageReceived:
return MessageReceived;
case LCEngineRealtimeHookType.ReceiversOffline:
return ReceiversOffline;
case LCEngineRealtimeHookType.MessageUpdate:
return MessageUpdate;
case LCEngineRealtimeHookType.ConversationStart:
return ConversationStart;
case LCEngineRealtimeHookType.ConversationStarted:
return ConversationStarted;
case LCEngineRealtimeHookType.ConversationAdd:
return ConversationAdd;
case LCEngineRealtimeHookType.ConversationAdded:
return ConversationAdded;
case LCEngineRealtimeHookType.ConversationRemove:
return ConversationRemove;
case LCEngineRealtimeHookType.ConversationRemoved:
return ConversationRemoved;
case LCEngineRealtimeHookType.ConversationUpdate:
return ConversationUpdate;
default:
throw new Exception($"Error hook type: {attr.HookType}");
}
})
.ToList()
.ForEach(item => {
Functions.TryAdd(item.Key, item.Value);
});
services.AddCors(options => {
options.AddPolicy(LCEngineCORS, builder => {
builder.AllowAnyOrigin()
.WithMethods(LCEngineCORSMethods)
.WithHeaders(LCEngineCORSHeaders)
.SetPreflightMaxAge(TimeSpan.FromSeconds(86400));
});
});
}
private static void PrintEnvironmentVar(string key) {
LCLogger.Debug($"{key} : {Environment.GetEnvironmentVariable(key)}");
}
internal static async Task<object> Invoke(MethodInfo mi, object[] parameters) {
try {
if (mi.ReturnType == typeof(Task) ||
(mi.ReturnType.IsGenericType && mi.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))) {
Task task = mi.Invoke(null, parameters) as Task;
await task;
return task.GetType().GetProperty("Result")?.GetValue(task);
}
return mi.Invoke(null, parameters);
} catch (TargetInvocationException e) {
Exception ex = e.InnerException;
if (ex is LCException lcEx) {
throw new Exception(JsonConvert.SerializeObject(new Dictionary<string, object> {
{ "code", lcEx.Code },
{ "message", lcEx.Message }
}));
}
throw ex;
}
}
internal static async Task<object> Invoke(MethodInfo mi, object request) {
try {
object[] ps = null;
if (mi.GetParameters().Length > 0) {
ps = new object[] { request };
}
if (mi.ReturnType == typeof(Task) ||
(mi.ReturnType.IsGenericType && mi.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))) {
Task task = mi.Invoke(null, ps) as Task;
await task;
return task.GetType().GetProperty("Result")?.GetValue(task);
}
return mi.Invoke(null, ps);
} catch (TargetInvocationException e) {
Exception ex = e.InnerException;
if (ex is LCException lcEx) {
throw new Exception(JsonConvert.SerializeObject(new Dictionary<string, object> {
{ "code", lcEx.Code },
{ "message", lcEx.Message }
}));
}
throw ex;
}
}
internal static Dictionary<string, object> Decode(JsonElement jsonElement) {
string json = System.Text.Json.JsonSerializer.Serialize(jsonElement);
Dictionary<string, object> dict = JsonConvert.DeserializeObject<Dictionary<string, object>>(json,
LCJsonConverter.Default);
return dict;
}
internal static void InitRequestContext(HttpRequest request) {
LCEngineRequestContext.Init();
LCEngineRequestContext.RemoteAddress = GetIP(request);
if (request.Headers.TryGetValue("x-lc-session", out StringValues session)) {
LCEngineRequestContext.SessionToken = session;
}
}
internal static string GetIP(HttpRequest request) {
if (request.Headers.TryGetValue("x-real-ip", out StringValues ip)) {
return ip.ToString();
}
if (request.Headers.TryGetValue("x-forwarded-for", out StringValues forward)) {
return forward.ToString();
}
return request.HttpContext.Connection.RemoteIpAddress.ToString();
}
internal static void CheckMasterKey(HttpRequest request) {
if (!request.Headers.TryGetValue(LCMasterKeyName, out StringValues masterKey)) {
throw new Exception("No master key");
}
if (!masterKey.Equals(Environment.GetEnvironmentVariable("LEANCLOUD_APP_MASTER_KEY"))) {
throw new Exception("Mismatch master key");
}
}
internal static void CheckHookKey(HttpRequest request) {
if (!request.Headers.TryGetValue(LCHookKeyName, out StringValues hookKey)) {
throw new Exception("No hook key");
}
if (!hookKey.Equals(Environment.GetEnvironmentVariable("LEANCLOUD_APP_HOOK_KEY"))) {
throw new Exception("Mismatch hook key");
}
}
internal static object GetFunctions(HttpRequest request) {
CheckMasterKey(request);
List<string> functions = new List<string>();
functions.AddRange(Functions.Keys);
functions.AddRange(ClassHooks.Keys);
functions.AddRange(UserHooks.Keys);
foreach (string func in functions) {
LCLogger.Debug(func);
}
return new Dictionary<string, List<string>> {
{ "result", functions }
};
}
}
}

View File

@ -0,0 +1,85 @@
using System.Collections.Generic;
using System.Threading;
using LeanCloud.Storage;
namespace LeanCloud.Engine {
/// <summary>
/// LCEngineRequestContext provides the context of engine request.
/// </summary>
public class LCEngineRequestContext {
public const string RemoteAddressKey = "__remoteAddressKey";
public const string SessionTokenKey = "__sessionToken";
public const string CurrentUserKey = "__currentUser";
private static ThreadLocal<Dictionary<string, object>> requestContext = new ThreadLocal<Dictionary<string, object>>();
public static void Init() {
if (requestContext.IsValueCreated) {
requestContext.Value.Clear();
}
requestContext.Value = new Dictionary<string, object>();
}
public static void Set(string key, object value) {
if (!requestContext.IsValueCreated) {
requestContext.Value = new Dictionary<string, object>();
}
requestContext.Value[key] = value;
}
public static object Get(string key) {
if (!requestContext.IsValueCreated) {
return null;
}
return requestContext.Value[key];
}
/// <summary>
/// The remote address of this request.
/// </summary>
public static string RemoteAddress {
get {
object remoteAddress = Get(RemoteAddressKey);
if (remoteAddress != null) {
return remoteAddress as string;
}
return null;
}
set {
Set(RemoteAddressKey, value);
}
}
/// <summary>
/// The session token of this request.
/// </summary>
public static string SessionToken {
get {
object sessionToken = Get(SessionTokenKey);
if (sessionToken != null) {
return sessionToken as string;
}
return null;
}
set {
Set(SessionTokenKey, value);
}
}
/// <summary>
/// The user of this request.
/// </summary>
public static LCUser CurrentUser {
get {
object currentUser = Get(CurrentUserKey);
if (currentUser != null) {
return currentUser as LCUser;
}
return null;
}
set {
Set(CurrentUserKey, value);
}
}
}
}

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 LeanCloud
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,79 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
namespace LC.Google.Protobuf
{
/// <summary>
/// Provides a utility routine to copy small arrays much more quickly than Buffer.BlockCopy
/// </summary>
internal static class ByteArray
{
/// <summary>
/// The threshold above which you should use Buffer.BlockCopy rather than ByteArray.Copy
/// </summary>
private const int CopyThreshold = 12;
/// <summary>
/// Determines which copy routine to use based on the number of bytes to be copied.
/// </summary>
internal static void Copy(byte[] src, int srcOffset, byte[] dst, int dstOffset, int count)
{
if (count > CopyThreshold)
{
Buffer.BlockCopy(src, srcOffset, dst, dstOffset, count);
}
else
{
int stop = srcOffset + count;
for (int i = srcOffset; i < stop; i++)
{
dst[dstOffset++] = src[i];
}
}
}
/// <summary>
/// Reverses the order of bytes in the array
/// </summary>
internal static void Reverse(byte[] bytes)
{
for (int first = 0, last = bytes.Length - 1; first < last; first++, last--)
{
byte temp = bytes[first];
bytes[first] = bytes[last];
bytes[last] = temp;
}
}
}
}

View File

@ -0,0 +1,434 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Security;
using System.Text;
#if !NET35
using System.Threading;
using System.Threading.Tasks;
#endif
#if NET35
using LC.Google.Protobuf.Compatibility;
#endif
namespace LC.Google.Protobuf
{
/// <summary>
/// Immutable array of bytes.
/// </summary>
public sealed class ByteString : IEnumerable<byte>, IEquatable<ByteString>
{
private static readonly ByteString empty = new ByteString(new byte[0]);
private readonly byte[] bytes;
/// <summary>
/// Unsafe operations that can cause IO Failure and/or other catastrophic side-effects.
/// </summary>
internal static class Unsafe
{
/// <summary>
/// Constructs a new ByteString from the given byte array. The array is
/// *not* copied, and must not be modified after this constructor is called.
/// </summary>
internal static ByteString FromBytes(byte[] bytes)
{
return new ByteString(bytes);
}
}
/// <summary>
/// Internal use only. Ensure that the provided array is not mutated and belongs to this instance.
/// </summary>
internal static ByteString AttachBytes(byte[] bytes)
{
return new ByteString(bytes);
}
/// <summary>
/// Constructs a new ByteString from the given byte array. The array is
/// *not* copied, and must not be modified after this constructor is called.
/// </summary>
private ByteString(byte[] bytes)
{
this.bytes = bytes;
}
/// <summary>
/// Returns an empty ByteString.
/// </summary>
public static ByteString Empty
{
get { return empty; }
}
/// <summary>
/// Returns the length of this ByteString in bytes.
/// </summary>
public int Length
{
get { return bytes.Length; }
}
/// <summary>
/// Returns <c>true</c> if this byte string is empty, <c>false</c> otherwise.
/// </summary>
public bool IsEmpty
{
get { return Length == 0; }
}
#if GOOGLE_PROTOBUF_SUPPORT_SYSTEM_MEMORY
/// <summary>
/// Provides read-only access to the data of this <see cref="ByteString"/>.
/// No data is copied so this is the most efficient way of accessing.
/// </summary>
public ReadOnlySpan<byte> Span
{
[SecuritySafeCritical]
get
{
return new ReadOnlySpan<byte>(bytes);
}
}
/// <summary>
/// Provides read-only access to the data of this <see cref="ByteString"/>.
/// No data is copied so this is the most efficient way of accessing.
/// </summary>
public ReadOnlyMemory<byte> Memory
{
[SecuritySafeCritical]
get
{
return new ReadOnlyMemory<byte>(bytes);
}
}
#endif
/// <summary>
/// Converts this <see cref="ByteString"/> into a byte array.
/// </summary>
/// <remarks>The data is copied - changes to the returned array will not be reflected in this <c>ByteString</c>.</remarks>
/// <returns>A byte array with the same data as this <c>ByteString</c>.</returns>
public byte[] ToByteArray()
{
return (byte[]) bytes.Clone();
}
/// <summary>
/// Converts this <see cref="ByteString"/> into a standard base64 representation.
/// </summary>
/// <returns>A base64 representation of this <c>ByteString</c>.</returns>
public string ToBase64()
{
return Convert.ToBase64String(bytes);
}
/// <summary>
/// Constructs a <see cref="ByteString" /> from the Base64 Encoded String.
/// </summary>
public static ByteString FromBase64(string bytes)
{
// By handling the empty string explicitly, we not only optimize but we fix a
// problem on CF 2.0. See issue 61 for details.
return bytes == "" ? Empty : new ByteString(Convert.FromBase64String(bytes));
}
/// <summary>
/// Constructs a <see cref="ByteString"/> from data in the given stream, synchronously.
/// </summary>
/// <remarks>If successful, <paramref name="stream"/> will be read completely, from the position
/// at the start of the call.</remarks>
/// <param name="stream">The stream to copy into a ByteString.</param>
/// <returns>A ByteString with content read from the given stream.</returns>
public static ByteString FromStream(Stream stream)
{
ProtoPreconditions.CheckNotNull(stream, nameof(stream));
int capacity = stream.CanSeek ? checked((int) (stream.Length - stream.Position)) : 0;
var memoryStream = new MemoryStream(capacity);
stream.CopyTo(memoryStream);
#if NETSTANDARD1_1 || NETSTANDARD2_0
byte[] bytes = memoryStream.ToArray();
#else
// Avoid an extra copy if we can.
byte[] bytes = memoryStream.Length == memoryStream.Capacity ? memoryStream.GetBuffer() : memoryStream.ToArray();
#endif
return AttachBytes(bytes);
}
#if !NET35
/// <summary>
/// Constructs a <see cref="ByteString"/> from data in the given stream, asynchronously.
/// </summary>
/// <remarks>If successful, <paramref name="stream"/> will be read completely, from the position
/// at the start of the call.</remarks>
/// <param name="stream">The stream to copy into a ByteString.</param>
/// <param name="cancellationToken">The cancellation token to use when reading from the stream, if any.</param>
/// <returns>A ByteString with content read from the given stream.</returns>
public async static Task<ByteString> FromStreamAsync(Stream stream, CancellationToken cancellationToken = default(CancellationToken))
{
ProtoPreconditions.CheckNotNull(stream, nameof(stream));
int capacity = stream.CanSeek ? checked((int) (stream.Length - stream.Position)) : 0;
var memoryStream = new MemoryStream(capacity);
// We have to specify the buffer size here, as there's no overload accepting the cancellation token
// alone. But it's documented to use 81920 by default if not specified.
await stream.CopyToAsync(memoryStream, 81920, cancellationToken);
#if NETSTANDARD1_1 || NETSTANDARD2_0
byte[] bytes = memoryStream.ToArray();
#else
// Avoid an extra copy if we can.
byte[] bytes = memoryStream.Length == memoryStream.Capacity ? memoryStream.GetBuffer() : memoryStream.ToArray();
#endif
return AttachBytes(bytes);
}
#endif
/// <summary>
/// Constructs a <see cref="ByteString" /> from the given array. The contents
/// are copied, so further modifications to the array will not
/// be reflected in the returned ByteString.
/// This method can also be invoked in <c>ByteString.CopyFrom(0xaa, 0xbb, ...)</c> form
/// which is primarily useful for testing.
/// </summary>
public static ByteString CopyFrom(params byte[] bytes)
{
return new ByteString((byte[]) bytes.Clone());
}
/// <summary>
/// Constructs a <see cref="ByteString" /> from a portion of a byte array.
/// </summary>
public static ByteString CopyFrom(byte[] bytes, int offset, int count)
{
byte[] portion = new byte[count];
ByteArray.Copy(bytes, offset, portion, 0, count);
return new ByteString(portion);
}
#if GOOGLE_PROTOBUF_SUPPORT_SYSTEM_MEMORY
/// <summary>
/// Constructs a <see cref="ByteString" /> from a read only span. The contents
/// are copied, so further modifications to the span will not
/// be reflected in the returned <see cref="ByteString" />.
/// </summary>
[SecuritySafeCritical]
public static ByteString CopyFrom(ReadOnlySpan<byte> bytes)
{
return new ByteString(bytes.ToArray());
}
#endif
/// <summary>
/// Creates a new <see cref="ByteString" /> by encoding the specified text with
/// the given encoding.
/// </summary>
public static ByteString CopyFrom(string text, Encoding encoding)
{
return new ByteString(encoding.GetBytes(text));
}
/// <summary>
/// Creates a new <see cref="ByteString" /> by encoding the specified text in UTF-8.
/// </summary>
public static ByteString CopyFromUtf8(string text)
{
return CopyFrom(text, Encoding.UTF8);
}
/// <summary>
/// Returns the byte at the given index.
/// </summary>
public byte this[int index]
{
get { return bytes[index]; }
}
/// <summary>
/// Converts this <see cref="ByteString"/> into a string by applying the given encoding.
/// </summary>
/// <remarks>
/// This method should only be used to convert binary data which was the result of encoding
/// text with the given encoding.
/// </remarks>
/// <param name="encoding">The encoding to use to decode the binary data into text.</param>
/// <returns>The result of decoding the binary data with the given decoding.</returns>
public string ToString(Encoding encoding)
{
return encoding.GetString(bytes, 0, bytes.Length);
}
/// <summary>
/// Converts this <see cref="ByteString"/> into a string by applying the UTF-8 encoding.
/// </summary>
/// <remarks>
/// This method should only be used to convert binary data which was the result of encoding
/// text with UTF-8.
/// </remarks>
/// <returns>The result of decoding the binary data with the given decoding.</returns>
public string ToStringUtf8()
{
return ToString(Encoding.UTF8);
}
/// <summary>
/// Returns an iterator over the bytes in this <see cref="ByteString"/>.
/// </summary>
/// <returns>An iterator over the bytes in this object.</returns>
public IEnumerator<byte> GetEnumerator()
{
return ((IEnumerable<byte>) bytes).GetEnumerator();
}
/// <summary>
/// Returns an iterator over the bytes in this <see cref="ByteString"/>.
/// </summary>
/// <returns>An iterator over the bytes in this object.</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// Creates a CodedInputStream from this ByteString's data.
/// </summary>
public CodedInputStream CreateCodedInput()
{
// We trust CodedInputStream not to reveal the provided byte array or modify it
return new CodedInputStream(bytes);
}
/// <summary>
/// Compares two byte strings for equality.
/// </summary>
/// <param name="lhs">The first byte string to compare.</param>
/// <param name="rhs">The second byte string to compare.</param>
/// <returns><c>true</c> if the byte strings are equal; false otherwise.</returns>
public static bool operator ==(ByteString lhs, ByteString rhs)
{
if (ReferenceEquals(lhs, rhs))
{
return true;
}
if (ReferenceEquals(lhs, null) || ReferenceEquals(rhs, null))
{
return false;
}
if (lhs.bytes.Length != rhs.bytes.Length)
{
return false;
}
for (int i = 0; i < lhs.Length; i++)
{
if (rhs.bytes[i] != lhs.bytes[i])
{
return false;
}
}
return true;
}
/// <summary>
/// Compares two byte strings for inequality.
/// </summary>
/// <param name="lhs">The first byte string to compare.</param>
/// <param name="rhs">The second byte string to compare.</param>
/// <returns><c>false</c> if the byte strings are equal; true otherwise.</returns>
public static bool operator !=(ByteString lhs, ByteString rhs)
{
return !(lhs == rhs);
}
/// <summary>
/// Compares this byte string with another object.
/// </summary>
/// <param name="obj">The object to compare this with.</param>
/// <returns><c>true</c> if <paramref name="obj"/> refers to an equal <see cref="ByteString"/>; <c>false</c> otherwise.</returns>
public override bool Equals(object obj)
{
return this == (obj as ByteString);
}
/// <summary>
/// Returns a hash code for this object. Two equal byte strings
/// will return the same hash code.
/// </summary>
/// <returns>A hash code for this object.</returns>
public override int GetHashCode()
{
int ret = 23;
foreach (byte b in bytes)
{
ret = (ret * 31) + b;
}
return ret;
}
/// <summary>
/// Compares this byte string with another.
/// </summary>
/// <param name="other">The <see cref="ByteString"/> to compare this with.</param>
/// <returns><c>true</c> if <paramref name="other"/> refers to an equal byte string; <c>false</c> otherwise.</returns>
public bool Equals(ByteString other)
{
return this == other;
}
/// <summary>
/// Used internally by CodedOutputStream to avoid creating a copy for the write
/// </summary>
internal void WriteRawBytesTo(CodedOutputStream outputStream)
{
outputStream.WriteRawBytes(bytes, 0, bytes.Length);
}
/// <summary>
/// Copies the entire byte array to the destination array provided at the offset specified.
/// </summary>
public void CopyTo(byte[] array, int position)
{
ByteArray.Copy(bytes, 0, array, position, bytes.Length);
}
/// <summary>
/// Writes the entire byte array to the provided stream
/// </summary>
public void WriteTo(Stream outputStream)
{
outputStream.Write(bytes, 0, bytes.Length);
}
}
}

View File

@ -0,0 +1,699 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using LC.Google.Protobuf.Collections;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
namespace LC.Google.Protobuf
{
/// <summary>
/// Reads and decodes protocol message fields.
/// </summary>
/// <remarks>
/// <para>
/// This class is generally used by generated code to read appropriate
/// primitives from the stream. It effectively encapsulates the lowest
/// levels of protocol buffer format.
/// </para>
/// <para>
/// Repeated fields and map fields are not handled by this class; use <see cref="RepeatedField{T}"/>
/// and <see cref="MapField{TKey, TValue}"/> to serialize such fields.
/// </para>
/// </remarks>
[SecuritySafeCritical]
public sealed class CodedInputStream : IDisposable
{
/// <summary>
/// Whether to leave the underlying stream open when disposing of this stream.
/// This is always true when there's no stream.
/// </summary>
private readonly bool leaveOpen;
/// <summary>
/// Buffer of data read from the stream or provided at construction time.
/// </summary>
private readonly byte[] buffer;
/// <summary>
/// The stream to read further input from, or null if the byte array buffer was provided
/// directly on construction, with no further data available.
/// </summary>
private readonly Stream input;
/// <summary>
/// The parser state is kept separately so that other parse implementations can reuse the same
/// parsing primitives.
/// </summary>
private ParserInternalState state;
internal const int DefaultRecursionLimit = 100;
internal const int DefaultSizeLimit = Int32.MaxValue;
internal const int BufferSize = 4096;
#region Construction
// Note that the checks are performed such that we don't end up checking obviously-valid things
// like non-null references for arrays we've just created.
/// <summary>
/// Creates a new CodedInputStream reading data from the given byte array.
/// </summary>
public CodedInputStream(byte[] buffer) : this(null, ProtoPreconditions.CheckNotNull(buffer, "buffer"), 0, buffer.Length, true)
{
}
/// <summary>
/// Creates a new <see cref="CodedInputStream"/> that reads from the given byte array slice.
/// </summary>
public CodedInputStream(byte[] buffer, int offset, int length)
: this(null, ProtoPreconditions.CheckNotNull(buffer, "buffer"), offset, offset + length, true)
{
if (offset < 0 || offset > buffer.Length)
{
throw new ArgumentOutOfRangeException("offset", "Offset must be within the buffer");
}
if (length < 0 || offset + length > buffer.Length)
{
throw new ArgumentOutOfRangeException("length", "Length must be non-negative and within the buffer");
}
}
/// <summary>
/// Creates a new <see cref="CodedInputStream"/> reading data from the given stream, which will be disposed
/// when the returned object is disposed.
/// </summary>
/// <param name="input">The stream to read from.</param>
public CodedInputStream(Stream input) : this(input, false)
{
}
/// <summary>
/// Creates a new <see cref="CodedInputStream"/> reading data from the given stream.
/// </summary>
/// <param name="input">The stream to read from.</param>
/// <param name="leaveOpen"><c>true</c> to leave <paramref name="input"/> open when the returned
/// <c cref="CodedInputStream"/> is disposed; <c>false</c> to dispose of the given stream when the
/// returned object is disposed.</param>
public CodedInputStream(Stream input, bool leaveOpen)
: this(ProtoPreconditions.CheckNotNull(input, "input"), new byte[BufferSize], 0, 0, leaveOpen)
{
}
/// <summary>
/// Creates a new CodedInputStream reading data from the given
/// stream and buffer, using the default limits.
/// </summary>
internal CodedInputStream(Stream input, byte[] buffer, int bufferPos, int bufferSize, bool leaveOpen)
{
this.input = input;
this.buffer = buffer;
this.state.bufferPos = bufferPos;
this.state.bufferSize = bufferSize;
this.state.sizeLimit = DefaultSizeLimit;
this.state.recursionLimit = DefaultRecursionLimit;
SegmentedBufferHelper.Initialize(this, out this.state.segmentedBufferHelper);
this.leaveOpen = leaveOpen;
this.state.currentLimit = int.MaxValue;
}
/// <summary>
/// Creates a new CodedInputStream reading data from the given
/// stream and buffer, using the specified limits.
/// </summary>
/// <remarks>
/// This chains to the version with the default limits instead of vice versa to avoid
/// having to check that the default values are valid every time.
/// </remarks>
internal CodedInputStream(Stream input, byte[] buffer, int bufferPos, int bufferSize, int sizeLimit, int recursionLimit, bool leaveOpen)
: this(input, buffer, bufferPos, bufferSize, leaveOpen)
{
if (sizeLimit <= 0)
{
throw new ArgumentOutOfRangeException("sizeLimit", "Size limit must be positive");
}
if (recursionLimit <= 0)
{
throw new ArgumentOutOfRangeException("recursionLimit!", "Recursion limit must be positive");
}
this.state.sizeLimit = sizeLimit;
this.state.recursionLimit = recursionLimit;
}
#endregion
/// <summary>
/// Creates a <see cref="CodedInputStream"/> with the specified size and recursion limits, reading
/// from an input stream.
/// </summary>
/// <remarks>
/// This method exists separately from the constructor to reduce the number of constructor overloads.
/// It is likely to be used considerably less frequently than the constructors, as the default limits
/// are suitable for most use cases.
/// </remarks>
/// <param name="input">The input stream to read from</param>
/// <param name="sizeLimit">The total limit of data to read from the stream.</param>
/// <param name="recursionLimit">The maximum recursion depth to allow while reading.</param>
/// <returns>A <c>CodedInputStream</c> reading from <paramref name="input"/> with the specified size
/// and recursion limits.</returns>
public static CodedInputStream CreateWithLimits(Stream input, int sizeLimit, int recursionLimit)
{
// Note: we may want an overload accepting leaveOpen
return new CodedInputStream(input, new byte[BufferSize], 0, 0, sizeLimit, recursionLimit, false);
}
/// <summary>
/// Returns the current position in the input stream, or the position in the input buffer
/// </summary>
public long Position
{
get
{
if (input != null)
{
return input.Position - ((state.bufferSize + state.bufferSizeAfterLimit) - state.bufferPos);
}
return state.bufferPos;
}
}
/// <summary>
/// Returns the last tag read, or 0 if no tags have been read or we've read beyond
/// the end of the stream.
/// </summary>
internal uint LastTag { get { return state.lastTag; } }
/// <summary>
/// Returns the size limit for this stream.
/// </summary>
/// <remarks>
/// This limit is applied when reading from the underlying stream, as a sanity check. It is
/// not applied when reading from a byte array data source without an underlying stream.
/// The default value is Int32.MaxValue.
/// </remarks>
/// <value>
/// The size limit.
/// </value>
public int SizeLimit { get { return state.sizeLimit; } }
/// <summary>
/// Returns the recursion limit for this stream. This limit is applied whilst reading messages,
/// to avoid maliciously-recursive data.
/// </summary>
/// <remarks>
/// The default limit is 100.
/// </remarks>
/// <value>
/// The recursion limit for this stream.
/// </value>
public int RecursionLimit { get { return state.recursionLimit; } }
/// <summary>
/// Internal-only property; when set to true, unknown fields will be discarded while parsing.
/// </summary>
internal bool DiscardUnknownFields
{
get { return state.DiscardUnknownFields; }
set { state.DiscardUnknownFields = value; }
}
/// <summary>
/// Internal-only property; provides extension identifiers to compatible messages while parsing.
/// </summary>
internal ExtensionRegistry ExtensionRegistry
{
get { return state.ExtensionRegistry; }
set { state.ExtensionRegistry = value; }
}
internal byte[] InternalBuffer => buffer;
internal Stream InternalInputStream => input;
internal ref ParserInternalState InternalState => ref state;
/// <summary>
/// Disposes of this instance, potentially closing any underlying stream.
/// </summary>
/// <remarks>
/// As there is no flushing to perform here, disposing of a <see cref="CodedInputStream"/> which
/// was constructed with the <c>leaveOpen</c> option parameter set to <c>true</c> (or one which
/// was constructed to read from a byte array) has no effect.
/// </remarks>
public void Dispose()
{
if (!leaveOpen)
{
input.Dispose();
}
}
#region Validation
/// <summary>
/// Verifies that the last call to ReadTag() returned tag 0 - in other words,
/// we've reached the end of the stream when we expected to.
/// </summary>
/// <exception cref="InvalidProtocolBufferException">The
/// tag read was not the one specified</exception>
internal void CheckReadEndOfStreamTag()
{
ParsingPrimitivesMessages.CheckReadEndOfStreamTag(ref state);
}
#endregion
#region Reading of tags etc
/// <summary>
/// Peeks at the next field tag. This is like calling <see cref="ReadTag"/>, but the
/// tag is not consumed. (So a subsequent call to <see cref="ReadTag"/> will return the
/// same value.)
/// </summary>
public uint PeekTag()
{
var span = new ReadOnlySpan<byte>(buffer);
return ParsingPrimitives.PeekTag(ref span, ref state);
}
/// <summary>
/// Reads a field tag, returning the tag of 0 for "end of stream".
/// </summary>
/// <remarks>
/// If this method returns 0, it doesn't necessarily mean the end of all
/// the data in this CodedInputStream; it may be the end of the logical stream
/// for an embedded message, for example.
/// </remarks>
/// <returns>The next field tag, or 0 for end of stream. (0 is never a valid tag.)</returns>
public uint ReadTag()
{
var span = new ReadOnlySpan<byte>(buffer);
return ParsingPrimitives.ParseTag(ref span, ref state);
}
/// <summary>
/// Skips the data for the field with the tag we've just read.
/// This should be called directly after <see cref="ReadTag"/>, when
/// the caller wishes to skip an unknown field.
/// </summary>
/// <remarks>
/// This method throws <see cref="InvalidProtocolBufferException"/> if the last-read tag was an end-group tag.
/// If a caller wishes to skip a group, they should skip the whole group, by calling this method after reading the
/// start-group tag. This behavior allows callers to call this method on any field they don't understand, correctly
/// resulting in an error if an end-group tag has not been paired with an earlier start-group tag.
/// </remarks>
/// <exception cref="InvalidProtocolBufferException">The last tag was an end-group tag</exception>
/// <exception cref="InvalidOperationException">The last read operation read to the end of the logical stream</exception>
public void SkipLastField()
{
var span = new ReadOnlySpan<byte>(buffer);
ParsingPrimitivesMessages.SkipLastField(ref span, ref state);
}
/// <summary>
/// Skip a group.
/// </summary>
internal void SkipGroup(uint startGroupTag)
{
var span = new ReadOnlySpan<byte>(buffer);
ParsingPrimitivesMessages.SkipGroup(ref span, ref state, startGroupTag);
}
/// <summary>
/// Reads a double field from the stream.
/// </summary>
public double ReadDouble()
{
var span = new ReadOnlySpan<byte>(buffer);
return ParsingPrimitives.ParseDouble(ref span, ref state);
}
/// <summary>
/// Reads a float field from the stream.
/// </summary>
public float ReadFloat()
{
var span = new ReadOnlySpan<byte>(buffer);
return ParsingPrimitives.ParseFloat(ref span, ref state);
}
/// <summary>
/// Reads a uint64 field from the stream.
/// </summary>
public ulong ReadUInt64()
{
return ReadRawVarint64();
}
/// <summary>
/// Reads an int64 field from the stream.
/// </summary>
public long ReadInt64()
{
return (long) ReadRawVarint64();
}
/// <summary>
/// Reads an int32 field from the stream.
/// </summary>
public int ReadInt32()
{
return (int) ReadRawVarint32();
}
/// <summary>
/// Reads a fixed64 field from the stream.
/// </summary>
public ulong ReadFixed64()
{
return ReadRawLittleEndian64();
}
/// <summary>
/// Reads a fixed32 field from the stream.
/// </summary>
public uint ReadFixed32()
{
return ReadRawLittleEndian32();
}
/// <summary>
/// Reads a bool field from the stream.
/// </summary>
public bool ReadBool()
{
return ReadRawVarint64() != 0;
}
/// <summary>
/// Reads a string field from the stream.
/// </summary>
public string ReadString()
{
var span = new ReadOnlySpan<byte>(buffer);
return ParsingPrimitives.ReadString(ref span, ref state);
}
/// <summary>
/// Reads an embedded message field value from the stream.
/// </summary>
public void ReadMessage(IMessage builder)
{
// TODO(jtattermusch): if the message doesn't implement IBufferMessage (and thus does not provide the InternalMergeFrom method),
// what we're doing here works fine, but could be more efficient.
// What happends is that we first initialize a ParseContext from the current coded input stream only to parse the length of the message, at which point
// we will need to switch back again to CodedInputStream-based parsing (which involves copying and storing the state) to be able to
// invoke the legacy MergeFrom(CodedInputStream) method.
// For now, this inefficiency is fine, considering this is only a backward-compatibility scenario (and regenerating the code fixes it).
var span = new ReadOnlySpan<byte>(buffer);
ParseContext.Initialize(ref span, ref state, out ParseContext ctx);
try
{
ParsingPrimitivesMessages.ReadMessage(ref ctx, builder);
}
finally
{
ctx.CopyStateTo(this);
}
}
/// <summary>
/// Reads an embedded group field from the stream.
/// </summary>
public void ReadGroup(IMessage builder)
{
ParseContext.Initialize(this, out ParseContext ctx);
try
{
ParsingPrimitivesMessages.ReadGroup(ref ctx, builder);
}
finally
{
ctx.CopyStateTo(this);
}
}
/// <summary>
/// Reads a bytes field value from the stream.
/// </summary>
public ByteString ReadBytes()
{
var span = new ReadOnlySpan<byte>(buffer);
return ParsingPrimitives.ReadBytes(ref span, ref state);
}
/// <summary>
/// Reads a uint32 field value from the stream.
/// </summary>
public uint ReadUInt32()
{
return ReadRawVarint32();
}
/// <summary>
/// Reads an enum field value from the stream.
/// </summary>
public int ReadEnum()
{
// Currently just a pass-through, but it's nice to separate it logically from WriteInt32.
return (int) ReadRawVarint32();
}
/// <summary>
/// Reads an sfixed32 field value from the stream.
/// </summary>
public int ReadSFixed32()
{
return (int) ReadRawLittleEndian32();
}
/// <summary>
/// Reads an sfixed64 field value from the stream.
/// </summary>
public long ReadSFixed64()
{
return (long) ReadRawLittleEndian64();
}
/// <summary>
/// Reads an sint32 field value from the stream.
/// </summary>
public int ReadSInt32()
{
return ParsingPrimitives.DecodeZigZag32(ReadRawVarint32());
}
/// <summary>
/// Reads an sint64 field value from the stream.
/// </summary>
public long ReadSInt64()
{
return ParsingPrimitives.DecodeZigZag64(ReadRawVarint64());
}
/// <summary>
/// Reads a length for length-delimited data.
/// </summary>
/// <remarks>
/// This is internally just reading a varint, but this method exists
/// to make the calling code clearer.
/// </remarks>
public int ReadLength()
{
var span = new ReadOnlySpan<byte>(buffer);
return ParsingPrimitives.ParseLength(ref span, ref state);
}
/// <summary>
/// Peeks at the next tag in the stream. If it matches <paramref name="tag"/>,
/// the tag is consumed and the method returns <c>true</c>; otherwise, the
/// stream is left in the original position and the method returns <c>false</c>.
/// </summary>
public bool MaybeConsumeTag(uint tag)
{
var span = new ReadOnlySpan<byte>(buffer);
return ParsingPrimitives.MaybeConsumeTag(ref span, ref state, tag);
}
#endregion
#region Underlying reading primitives
/// <summary>
/// Reads a raw Varint from the stream. If larger than 32 bits, discard the upper bits.
/// This method is optimised for the case where we've got lots of data in the buffer.
/// That means we can check the size just once, then just read directly from the buffer
/// without constant rechecking of the buffer length.
/// </summary>
internal uint ReadRawVarint32()
{
var span = new ReadOnlySpan<byte>(buffer);
return ParsingPrimitives.ParseRawVarint32(ref span, ref state);
}
/// <summary>
/// Reads a varint from the input one byte at a time, so that it does not
/// read any bytes after the end of the varint. If you simply wrapped the
/// stream in a CodedInputStream and used ReadRawVarint32(Stream)
/// then you would probably end up reading past the end of the varint since
/// CodedInputStream buffers its input.
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
internal static uint ReadRawVarint32(Stream input)
{
return ParsingPrimitives.ReadRawVarint32(input);
}
/// <summary>
/// Reads a raw varint from the stream.
/// </summary>
internal ulong ReadRawVarint64()
{
var span = new ReadOnlySpan<byte>(buffer);
return ParsingPrimitives.ParseRawVarint64(ref span, ref state);
}
/// <summary>
/// Reads a 32-bit little-endian integer from the stream.
/// </summary>
internal uint ReadRawLittleEndian32()
{
var span = new ReadOnlySpan<byte>(buffer);
return ParsingPrimitives.ParseRawLittleEndian32(ref span, ref state);
}
/// <summary>
/// Reads a 64-bit little-endian integer from the stream.
/// </summary>
internal ulong ReadRawLittleEndian64()
{
var span = new ReadOnlySpan<byte>(buffer);
return ParsingPrimitives.ParseRawLittleEndian64(ref span, ref state);
}
#endregion
#region Internal reading and buffer management
/// <summary>
/// Sets currentLimit to (current position) + byteLimit. This is called
/// when descending into a length-delimited embedded message. The previous
/// limit is returned.
/// </summary>
/// <returns>The old limit.</returns>
internal int PushLimit(int byteLimit)
{
return SegmentedBufferHelper.PushLimit(ref state, byteLimit);
}
/// <summary>
/// Discards the current limit, returning the previous limit.
/// </summary>
internal void PopLimit(int oldLimit)
{
SegmentedBufferHelper.PopLimit(ref state, oldLimit);
}
/// <summary>
/// Returns whether or not all the data before the limit has been read.
/// </summary>
/// <returns></returns>
internal bool ReachedLimit
{
get
{
return SegmentedBufferHelper.IsReachedLimit(ref state);
}
}
/// <summary>
/// Returns true if the stream has reached the end of the input. This is the
/// case if either the end of the underlying input source has been reached or
/// the stream has reached a limit created using PushLimit.
/// </summary>
public bool IsAtEnd
{
get
{
var span = new ReadOnlySpan<byte>(buffer);
return SegmentedBufferHelper.IsAtEnd(ref span, ref state);
}
}
/// <summary>
/// Called when buffer is empty to read more bytes from the
/// input. If <paramref name="mustSucceed"/> is true, RefillBuffer() guarantees that
/// either there will be at least one byte in the buffer when it returns
/// or it will throw an exception. If <paramref name="mustSucceed"/> is false,
/// RefillBuffer() returns false if no more bytes were available.
/// </summary>
/// <param name="mustSucceed"></param>
/// <returns></returns>
private bool RefillBuffer(bool mustSucceed)
{
var span = new ReadOnlySpan<byte>(buffer);
return state.segmentedBufferHelper.RefillBuffer(ref span, ref state, mustSucceed);
}
/// <summary>
/// Reads a fixed size of bytes from the input.
/// </summary>
/// <exception cref="InvalidProtocolBufferException">
/// the end of the stream or the current limit was reached
/// </exception>
internal byte[] ReadRawBytes(int size)
{
var span = new ReadOnlySpan<byte>(buffer);
return ParsingPrimitives.ReadRawBytes(ref span, ref state, size);
}
/// <summary>
/// Reads a top-level message or a nested message after the limits for this message have been pushed.
/// (parser will proceed until the end of the current limit)
/// NOTE: this method needs to be public because it's invoked by the generated code - e.g. msg.MergeFrom(CodedInputStream input) method
/// </summary>
public void ReadRawMessage(IMessage message)
{
ParseContext.Initialize(this, out ParseContext ctx);
try
{
ParsingPrimitivesMessages.ReadRawMessage(ref ctx, message);
}
finally
{
ctx.CopyStateTo(this);
}
}
#endregion
}
}

View File

@ -0,0 +1,308 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
namespace LC.Google.Protobuf
{
// This part of CodedOutputStream provides all the static entry points that are used
// by generated code and internally to compute the size of messages prior to being
// written to an instance of CodedOutputStream.
public sealed partial class CodedOutputStream
{
private const int LittleEndian64Size = 8;
private const int LittleEndian32Size = 4;
internal const int DoubleSize = LittleEndian64Size;
internal const int FloatSize = LittleEndian32Size;
internal const int BoolSize = 1;
/// <summary>
/// Computes the number of bytes that would be needed to encode a
/// double field, including the tag.
/// </summary>
public static int ComputeDoubleSize(double value)
{
return DoubleSize;
}
/// <summary>
/// Computes the number of bytes that would be needed to encode a
/// float field, including the tag.
/// </summary>
public static int ComputeFloatSize(float value)
{
return FloatSize;
}
/// <summary>
/// Computes the number of bytes that would be needed to encode a
/// uint64 field, including the tag.
/// </summary>
public static int ComputeUInt64Size(ulong value)
{
return ComputeRawVarint64Size(value);
}
/// <summary>
/// Computes the number of bytes that would be needed to encode an
/// int64 field, including the tag.
/// </summary>
public static int ComputeInt64Size(long value)
{
return ComputeRawVarint64Size((ulong) value);
}
/// <summary>
/// Computes the number of bytes that would be needed to encode an
/// int32 field, including the tag.
/// </summary>
public static int ComputeInt32Size(int value)
{
if (value >= 0)
{
return ComputeRawVarint32Size((uint) value);
}
else
{
// Must sign-extend.
return 10;
}
}
/// <summary>
/// Computes the number of bytes that would be needed to encode a
/// fixed64 field, including the tag.
/// </summary>
public static int ComputeFixed64Size(ulong value)
{
return LittleEndian64Size;
}
/// <summary>
/// Computes the number of bytes that would be needed to encode a
/// fixed32 field, including the tag.
/// </summary>
public static int ComputeFixed32Size(uint value)
{
return LittleEndian32Size;
}
/// <summary>
/// Computes the number of bytes that would be needed to encode a
/// bool field, including the tag.
/// </summary>
public static int ComputeBoolSize(bool value)
{
return BoolSize;
}
/// <summary>
/// Computes the number of bytes that would be needed to encode a
/// string field, including the tag.
/// </summary>
public static int ComputeStringSize(String value)
{
int byteArraySize = WritingPrimitives.Utf8Encoding.GetByteCount(value);
return ComputeLengthSize(byteArraySize) + byteArraySize;
}
/// <summary>
/// Computes the number of bytes that would be needed to encode a
/// group field, including the tag.
/// </summary>
public static int ComputeGroupSize(IMessage value)
{
return value.CalculateSize();
}
/// <summary>
/// Computes the number of bytes that would be needed to encode an
/// embedded message field, including the tag.
/// </summary>
public static int ComputeMessageSize(IMessage value)
{
int size = value.CalculateSize();
return ComputeLengthSize(size) + size;
}
/// <summary>
/// Computes the number of bytes that would be needed to encode a
/// bytes field, including the tag.
/// </summary>
public static int ComputeBytesSize(ByteString value)
{
return ComputeLengthSize(value.Length) + value.Length;
}
/// <summary>
/// Computes the number of bytes that would be needed to encode a
/// uint32 field, including the tag.
/// </summary>
public static int ComputeUInt32Size(uint value)
{
return ComputeRawVarint32Size(value);
}
/// <summary>
/// Computes the number of bytes that would be needed to encode a
/// enum field, including the tag. The caller is responsible for
/// converting the enum value to its numeric value.
/// </summary>
public static int ComputeEnumSize(int value)
{
// Currently just a pass-through, but it's nice to separate it logically.
return ComputeInt32Size(value);
}
/// <summary>
/// Computes the number of bytes that would be needed to encode an
/// sfixed32 field, including the tag.
/// </summary>
public static int ComputeSFixed32Size(int value)
{
return LittleEndian32Size;
}
/// <summary>
/// Computes the number of bytes that would be needed to encode an
/// sfixed64 field, including the tag.
/// </summary>
public static int ComputeSFixed64Size(long value)
{
return LittleEndian64Size;
}
/// <summary>
/// Computes the number of bytes that would be needed to encode an
/// sint32 field, including the tag.
/// </summary>
public static int ComputeSInt32Size(int value)
{
return ComputeRawVarint32Size(WritingPrimitives.EncodeZigZag32(value));
}
/// <summary>
/// Computes the number of bytes that would be needed to encode an
/// sint64 field, including the tag.
/// </summary>
public static int ComputeSInt64Size(long value)
{
return ComputeRawVarint64Size(WritingPrimitives.EncodeZigZag64(value));
}
/// <summary>
/// Computes the number of bytes that would be needed to encode a length,
/// as written by <see cref="WriteLength"/>.
/// </summary>
public static int ComputeLengthSize(int length)
{
return ComputeRawVarint32Size((uint) length);
}
/// <summary>
/// Computes the number of bytes that would be needed to encode a varint.
/// </summary>
public static int ComputeRawVarint32Size(uint value)
{
if ((value & (0xffffffff << 7)) == 0)
{
return 1;
}
if ((value & (0xffffffff << 14)) == 0)
{
return 2;
}
if ((value & (0xffffffff << 21)) == 0)
{
return 3;
}
if ((value & (0xffffffff << 28)) == 0)
{
return 4;
}
return 5;
}
/// <summary>
/// Computes the number of bytes that would be needed to encode a varint.
/// </summary>
public static int ComputeRawVarint64Size(ulong value)
{
if ((value & (0xffffffffffffffffL << 7)) == 0)
{
return 1;
}
if ((value & (0xffffffffffffffffL << 14)) == 0)
{
return 2;
}
if ((value & (0xffffffffffffffffL << 21)) == 0)
{
return 3;
}
if ((value & (0xffffffffffffffffL << 28)) == 0)
{
return 4;
}
if ((value & (0xffffffffffffffffL << 35)) == 0)
{
return 5;
}
if ((value & (0xffffffffffffffffL << 42)) == 0)
{
return 6;
}
if ((value & (0xffffffffffffffffL << 49)) == 0)
{
return 7;
}
if ((value & (0xffffffffffffffffL << 56)) == 0)
{
return 8;
}
if ((value & (0xffffffffffffffffL << 63)) == 0)
{
return 9;
}
return 10;
}
/// <summary>
/// Computes the number of bytes that would be needed to encode a tag.
/// </summary>
public static int ComputeTagSize(int fieldNumber)
{
return ComputeRawVarint32Size(WireFormat.MakeTag(fieldNumber, 0));
}
}
}

View File

@ -0,0 +1,607 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using LC.Google.Protobuf.Collections;
using System;
using System.IO;
using System.Security;
using System.Text;
namespace LC.Google.Protobuf
{
/// <summary>
/// Encodes and writes protocol message fields.
/// </summary>
/// <remarks>
/// <para>
/// This class is generally used by generated code to write appropriate
/// primitives to the stream. It effectively encapsulates the lowest
/// levels of protocol buffer format. Unlike some other implementations,
/// this does not include combined "write tag and value" methods. Generated
/// code knows the exact byte representations of the tags they're going to write,
/// so there's no need to re-encode them each time. Manually-written code calling
/// this class should just call one of the <c>WriteTag</c> overloads before each value.
/// </para>
/// <para>
/// Repeated fields and map fields are not handled by this class; use <c>RepeatedField&lt;T&gt;</c>
/// and <c>MapField&lt;TKey, TValue&gt;</c> to serialize such fields.
/// </para>
/// </remarks>
[SecuritySafeCritical]
public sealed partial class CodedOutputStream : IDisposable
{
/// <summary>
/// The buffer size used by CreateInstance(Stream).
/// </summary>
public static readonly int DefaultBufferSize = 4096;
private readonly bool leaveOpen;
private readonly byte[] buffer;
private WriterInternalState state;
private readonly Stream output;
#region Construction
/// <summary>
/// Creates a new CodedOutputStream that writes directly to the given
/// byte array. If more bytes are written than fit in the array,
/// OutOfSpaceException will be thrown.
/// </summary>
public CodedOutputStream(byte[] flatArray) : this(flatArray, 0, flatArray.Length)
{
}
/// <summary>
/// Creates a new CodedOutputStream that writes directly to the given
/// byte array slice. If more bytes are written than fit in the array,
/// OutOfSpaceException will be thrown.
/// </summary>
private CodedOutputStream(byte[] buffer, int offset, int length)
{
this.output = null;
this.buffer = ProtoPreconditions.CheckNotNull(buffer, nameof(buffer));
this.state.position = offset;
this.state.limit = offset + length;
WriteBufferHelper.Initialize(this, out this.state.writeBufferHelper);
leaveOpen = true; // Simple way of avoiding trying to dispose of a null reference
}
private CodedOutputStream(Stream output, byte[] buffer, bool leaveOpen)
{
this.output = ProtoPreconditions.CheckNotNull(output, nameof(output));
this.buffer = buffer;
this.state.position = 0;
this.state.limit = buffer.Length;
WriteBufferHelper.Initialize(this, out this.state.writeBufferHelper);
this.leaveOpen = leaveOpen;
}
/// <summary>
/// Creates a new <see cref="CodedOutputStream" /> which write to the given stream, and disposes of that
/// stream when the returned <c>CodedOutputStream</c> is disposed.
/// </summary>
/// <param name="output">The stream to write to. It will be disposed when the returned <c>CodedOutputStream is disposed.</c></param>
public CodedOutputStream(Stream output) : this(output, DefaultBufferSize, false)
{
}
/// <summary>
/// Creates a new CodedOutputStream which write to the given stream and uses
/// the specified buffer size.
/// </summary>
/// <param name="output">The stream to write to. It will be disposed when the returned <c>CodedOutputStream is disposed.</c></param>
/// <param name="bufferSize">The size of buffer to use internally.</param>
public CodedOutputStream(Stream output, int bufferSize) : this(output, new byte[bufferSize], false)
{
}
/// <summary>
/// Creates a new CodedOutputStream which write to the given stream.
/// </summary>
/// <param name="output">The stream to write to.</param>
/// <param name="leaveOpen">If <c>true</c>, <paramref name="output"/> is left open when the returned <c>CodedOutputStream</c> is disposed;
/// if <c>false</c>, the provided stream is disposed as well.</param>
public CodedOutputStream(Stream output, bool leaveOpen) : this(output, DefaultBufferSize, leaveOpen)
{
}
/// <summary>
/// Creates a new CodedOutputStream which write to the given stream and uses
/// the specified buffer size.
/// </summary>
/// <param name="output">The stream to write to.</param>
/// <param name="bufferSize">The size of buffer to use internally.</param>
/// <param name="leaveOpen">If <c>true</c>, <paramref name="output"/> is left open when the returned <c>CodedOutputStream</c> is disposed;
/// if <c>false</c>, the provided stream is disposed as well.</param>
public CodedOutputStream(Stream output, int bufferSize, bool leaveOpen) : this(output, new byte[bufferSize], leaveOpen)
{
}
#endregion
/// <summary>
/// Returns the current position in the stream, or the position in the output buffer
/// </summary>
public long Position
{
get
{
if (output != null)
{
return output.Position + state.position;
}
return state.position;
}
}
#region Writing of values (not including tags)
/// <summary>
/// Writes a double field value, without a tag, to the stream.
/// </summary>
/// <param name="value">The value to write</param>
public void WriteDouble(double value)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteDouble(ref span, ref state, value);
}
/// <summary>
/// Writes a float field value, without a tag, to the stream.
/// </summary>
/// <param name="value">The value to write</param>
public void WriteFloat(float value)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteFloat(ref span, ref state, value);
}
/// <summary>
/// Writes a uint64 field value, without a tag, to the stream.
/// </summary>
/// <param name="value">The value to write</param>
public void WriteUInt64(ulong value)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteUInt64(ref span, ref state, value);
}
/// <summary>
/// Writes an int64 field value, without a tag, to the stream.
/// </summary>
/// <param name="value">The value to write</param>
public void WriteInt64(long value)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteInt64(ref span, ref state, value);
}
/// <summary>
/// Writes an int32 field value, without a tag, to the stream.
/// </summary>
/// <param name="value">The value to write</param>
public void WriteInt32(int value)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteInt32(ref span, ref state, value);
}
/// <summary>
/// Writes a fixed64 field value, without a tag, to the stream.
/// </summary>
/// <param name="value">The value to write</param>
public void WriteFixed64(ulong value)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteFixed64(ref span, ref state, value);
}
/// <summary>
/// Writes a fixed32 field value, without a tag, to the stream.
/// </summary>
/// <param name="value">The value to write</param>
public void WriteFixed32(uint value)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteFixed32(ref span, ref state, value);
}
/// <summary>
/// Writes a bool field value, without a tag, to the stream.
/// </summary>
/// <param name="value">The value to write</param>
public void WriteBool(bool value)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteBool(ref span, ref state, value);
}
/// <summary>
/// Writes a string field value, without a tag, to the stream.
/// The data is length-prefixed.
/// </summary>
/// <param name="value">The value to write</param>
public void WriteString(string value)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteString(ref span, ref state, value);
}
/// <summary>
/// Writes a message, without a tag, to the stream.
/// The data is length-prefixed.
/// </summary>
/// <param name="value">The value to write</param>
public void WriteMessage(IMessage value)
{
// TODO(jtattermusch): if the message doesn't implement IBufferMessage (and thus does not provide the InternalWriteTo method),
// what we're doing here works fine, but could be more efficient.
// For now, this inefficiency is fine, considering this is only a backward-compatibility scenario (and regenerating the code fixes it).
var span = new Span<byte>(buffer);
WriteContext.Initialize(ref span, ref state, out WriteContext ctx);
try
{
WritingPrimitivesMessages.WriteMessage(ref ctx, value);
}
finally
{
ctx.CopyStateTo(this);
}
}
/// <summary>
/// Writes a message, without a tag, to the stream.
/// Only the message data is written, without a length-delimiter.
/// </summary>
/// <param name="value">The value to write</param>
public void WriteRawMessage(IMessage value)
{
// TODO(jtattermusch): if the message doesn't implement IBufferMessage (and thus does not provide the InternalWriteTo method),
// what we're doing here works fine, but could be more efficient.
// For now, this inefficiency is fine, considering this is only a backward-compatibility scenario (and regenerating the code fixes it).
var span = new Span<byte>(buffer);
WriteContext.Initialize(ref span, ref state, out WriteContext ctx);
try
{
WritingPrimitivesMessages.WriteRawMessage(ref ctx, value);
}
finally
{
ctx.CopyStateTo(this);
}
}
/// <summary>
/// Writes a group, without a tag, to the stream.
/// </summary>
/// <param name="value">The value to write</param>
public void WriteGroup(IMessage value)
{
var span = new Span<byte>(buffer);
WriteContext.Initialize(ref span, ref state, out WriteContext ctx);
try
{
WritingPrimitivesMessages.WriteGroup(ref ctx, value);
}
finally
{
ctx.CopyStateTo(this);
}
}
/// <summary>
/// Write a byte string, without a tag, to the stream.
/// The data is length-prefixed.
/// </summary>
/// <param name="value">The value to write</param>
public void WriteBytes(ByteString value)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteBytes(ref span, ref state, value);
}
/// <summary>
/// Writes a uint32 value, without a tag, to the stream.
/// </summary>
/// <param name="value">The value to write</param>
public void WriteUInt32(uint value)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteUInt32(ref span, ref state, value);
}
/// <summary>
/// Writes an enum value, without a tag, to the stream.
/// </summary>
/// <param name="value">The value to write</param>
public void WriteEnum(int value)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteEnum(ref span, ref state, value);
}
/// <summary>
/// Writes an sfixed32 value, without a tag, to the stream.
/// </summary>
/// <param name="value">The value to write.</param>
public void WriteSFixed32(int value)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteSFixed32(ref span, ref state, value);
}
/// <summary>
/// Writes an sfixed64 value, without a tag, to the stream.
/// </summary>
/// <param name="value">The value to write</param>
public void WriteSFixed64(long value)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteSFixed64(ref span, ref state, value);
}
/// <summary>
/// Writes an sint32 value, without a tag, to the stream.
/// </summary>
/// <param name="value">The value to write</param>
public void WriteSInt32(int value)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteSInt32(ref span, ref state, value);
}
/// <summary>
/// Writes an sint64 value, without a tag, to the stream.
/// </summary>
/// <param name="value">The value to write</param>
public void WriteSInt64(long value)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteSInt64(ref span, ref state, value);
}
/// <summary>
/// Writes a length (in bytes) for length-delimited data.
/// </summary>
/// <remarks>
/// This method simply writes a rawint, but exists for clarity in calling code.
/// </remarks>
/// <param name="length">Length value, in bytes.</param>
public void WriteLength(int length)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteLength(ref span, ref state, length);
}
#endregion
#region Raw tag writing
/// <summary>
/// Encodes and writes a tag.
/// </summary>
/// <param name="fieldNumber">The number of the field to write the tag for</param>
/// <param name="type">The wire format type of the tag to write</param>
public void WriteTag(int fieldNumber, WireFormat.WireType type)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteTag(ref span, ref state, fieldNumber, type);
}
/// <summary>
/// Writes an already-encoded tag.
/// </summary>
/// <param name="tag">The encoded tag</param>
public void WriteTag(uint tag)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteTag(ref span, ref state, tag);
}
/// <summary>
/// Writes the given single-byte tag directly to the stream.
/// </summary>
/// <param name="b1">The encoded tag</param>
public void WriteRawTag(byte b1)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteRawTag(ref span, ref state, b1);
}
/// <summary>
/// Writes the given two-byte tag directly to the stream.
/// </summary>
/// <param name="b1">The first byte of the encoded tag</param>
/// <param name="b2">The second byte of the encoded tag</param>
public void WriteRawTag(byte b1, byte b2)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteRawTag(ref span, ref state, b1, b2);
}
/// <summary>
/// Writes the given three-byte tag directly to the stream.
/// </summary>
/// <param name="b1">The first byte of the encoded tag</param>
/// <param name="b2">The second byte of the encoded tag</param>
/// <param name="b3">The third byte of the encoded tag</param>
public void WriteRawTag(byte b1, byte b2, byte b3)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteRawTag(ref span, ref state, b1, b2, b3);
}
/// <summary>
/// Writes the given four-byte tag directly to the stream.
/// </summary>
/// <param name="b1">The first byte of the encoded tag</param>
/// <param name="b2">The second byte of the encoded tag</param>
/// <param name="b3">The third byte of the encoded tag</param>
/// <param name="b4">The fourth byte of the encoded tag</param>
public void WriteRawTag(byte b1, byte b2, byte b3, byte b4)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteRawTag(ref span, ref state, b1, b2, b3, b4);
}
/// <summary>
/// Writes the given five-byte tag directly to the stream.
/// </summary>
/// <param name="b1">The first byte of the encoded tag</param>
/// <param name="b2">The second byte of the encoded tag</param>
/// <param name="b3">The third byte of the encoded tag</param>
/// <param name="b4">The fourth byte of the encoded tag</param>
/// <param name="b5">The fifth byte of the encoded tag</param>
public void WriteRawTag(byte b1, byte b2, byte b3, byte b4, byte b5)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteRawTag(ref span, ref state, b1, b2, b3, b4, b5);
}
#endregion
#region Underlying writing primitives
/// <summary>
/// Writes a 32 bit value as a varint. The fast route is taken when
/// there's enough buffer space left to whizz through without checking
/// for each byte; otherwise, we resort to calling WriteRawByte each time.
/// </summary>
internal void WriteRawVarint32(uint value)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteRawVarint32(ref span, ref state, value);
}
internal void WriteRawVarint64(ulong value)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteRawVarint64(ref span, ref state, value);
}
internal void WriteRawLittleEndian32(uint value)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteRawLittleEndian32(ref span, ref state, value);
}
internal void WriteRawLittleEndian64(ulong value)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteRawLittleEndian64(ref span, ref state, value);
}
/// <summary>
/// Writes out an array of bytes.
/// </summary>
internal void WriteRawBytes(byte[] value)
{
WriteRawBytes(value, 0, value.Length);
}
/// <summary>
/// Writes out part of an array of bytes.
/// </summary>
internal void WriteRawBytes(byte[] value, int offset, int length)
{
var span = new Span<byte>(buffer);
WritingPrimitives.WriteRawBytes(ref span, ref state, value, offset, length);
}
#endregion
/// <summary>
/// Indicates that a CodedOutputStream wrapping a flat byte array
/// ran out of space.
/// </summary>
public sealed class OutOfSpaceException : IOException
{
internal OutOfSpaceException()
: base("CodedOutputStream was writing to a flat byte array and ran out of space.")
{
}
}
/// <summary>
/// Flushes any buffered data and optionally closes the underlying stream, if any.
/// </summary>
/// <remarks>
/// <para>
/// By default, any underlying stream is closed by this method. To configure this behaviour,
/// use a constructor overload with a <c>leaveOpen</c> parameter. If this instance does not
/// have an underlying stream, this method does nothing.
/// </para>
/// <para>
/// For the sake of efficiency, calling this method does not prevent future write calls - but
/// if a later write ends up writing to a stream which has been disposed, that is likely to
/// fail. It is recommend that you not call any other methods after this.
/// </para>
/// </remarks>
public void Dispose()
{
Flush();
if (!leaveOpen)
{
output.Dispose();
}
}
/// <summary>
/// Flushes any buffered data to the underlying stream (if there is one).
/// </summary>
public void Flush()
{
var span = new Span<byte>(buffer);
WriteBufferHelper.Flush(ref span, ref state);
}
/// <summary>
/// Verifies that SpaceLeft returns zero. It's common to create a byte array
/// that is exactly big enough to hold a message, then write to it with
/// a CodedOutputStream. Calling CheckNoSpaceLeft after writing verifies that
/// the message was actually as big as expected, which can help finding bugs.
/// </summary>
public void CheckNoSpaceLeft()
{
WriteBufferHelper.CheckNoSpaceLeft(ref state);
}
/// <summary>
/// If writing to a flat array, returns the space left in the array. Otherwise,
/// throws an InvalidOperationException.
/// </summary>
public int SpaceLeft => WriteBufferHelper.GetSpaceLeft(ref state);
internal byte[] InternalBuffer => buffer;
internal Stream InternalOutputStream => output;
internal ref WriterInternalState InternalState => ref state;
}
}

View File

@ -0,0 +1,89 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2017 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace LC.Google.Protobuf.Collections
{
/// <summary>
/// Utility to compare if two Lists are the same, and the hash code
/// of a List.
/// </summary>
public static class Lists
{
/// <summary>
/// Checks if two lists are equal.
/// </summary>
public static bool Equals<T>(List<T> left, List<T> right)
{
if (left == right)
{
return true;
}
if (left == null || right == null)
{
return false;
}
if (left.Count != right.Count)
{
return false;
}
IEqualityComparer<T> comparer = EqualityComparer<T>.Default;
for (int i = 0; i < left.Count; i++)
{
if (!comparer.Equals(left[i], right[i]))
{
return false;
}
}
return true;
}
/// <summary>
/// Gets the list's hash code.
/// </summary>
public static int GetHashCode<T>(List<T> list)
{
if (list == null)
{
return 0;
}
int hash = 31;
foreach (T element in list)
{
hash = hash * 29 + element.GetHashCode();
}
return hash;
}
}
}

View File

@ -0,0 +1,841 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using LC.Google.Protobuf.Compatibility;
using LC.Google.Protobuf.Reflection;
using System;
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security;
namespace LC.Google.Protobuf.Collections
{
/// <summary>
/// Representation of a map field in a Protocol Buffer message.
/// </summary>
/// <typeparam name="TKey">Key type in the map. Must be a type supported by Protocol Buffer map keys.</typeparam>
/// <typeparam name="TValue">Value type in the map. Must be a type supported by Protocol Buffers.</typeparam>
/// <remarks>
/// <para>
/// For string keys, the equality comparison is provided by <see cref="StringComparer.Ordinal" />.
/// </para>
/// <para>
/// Null values are not permitted in the map, either for wrapper types or regular messages.
/// If a map is deserialized from a data stream and the value is missing from an entry, a default value
/// is created instead. For primitive types, that is the regular default value (0, the empty string and so
/// on); for message types, an empty instance of the message is created, as if the map entry contained a 0-length
/// encoded value for the field.
/// </para>
/// <para>
/// This implementation does not generally prohibit the use of key/value types which are not
/// supported by Protocol Buffers (e.g. using a key type of <code>byte</code>) but nor does it guarantee
/// that all operations will work in such cases.
/// </para>
/// <para>
/// The order in which entries are returned when iterating over this object is undefined, and may change
/// in future versions.
/// </para>
/// </remarks>
public sealed class MapField<TKey, TValue> : IDeepCloneable<MapField<TKey, TValue>>, IDictionary<TKey, TValue>, IEquatable<MapField<TKey, TValue>>, IDictionary
#if !NET35
, IReadOnlyDictionary<TKey, TValue>
#endif
{
private static readonly EqualityComparer<TValue> ValueEqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<TValue>();
private static readonly EqualityComparer<TKey> KeyEqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<TKey>();
// TODO: Don't create the map/list until we have an entry. (Assume many maps will be empty.)
private readonly Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> map =
new Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>(KeyEqualityComparer);
private readonly LinkedList<KeyValuePair<TKey, TValue>> list = new LinkedList<KeyValuePair<TKey, TValue>>();
/// <summary>
/// Creates a deep clone of this object.
/// </summary>
/// <returns>
/// A deep clone of this object.
/// </returns>
public MapField<TKey, TValue> Clone()
{
var clone = new MapField<TKey, TValue>();
// Keys are never cloneable. Values might be.
if (typeof(IDeepCloneable<TValue>).IsAssignableFrom(typeof(TValue)))
{
foreach (var pair in list)
{
clone.Add(pair.Key, ((IDeepCloneable<TValue>)pair.Value).Clone());
}
}
else
{
// Nothing is cloneable, so we don't need to worry.
clone.Add(this);
}
return clone;
}
/// <summary>
/// Adds the specified key/value pair to the map.
/// </summary>
/// <remarks>
/// This operation fails if the key already exists in the map. To replace an existing entry, use the indexer.
/// </remarks>
/// <param name="key">The key to add</param>
/// <param name="value">The value to add.</param>
/// <exception cref="System.ArgumentException">The given key already exists in map.</exception>
public void Add(TKey key, TValue value)
{
// Validation of arguments happens in ContainsKey and the indexer
if (ContainsKey(key))
{
throw new ArgumentException("Key already exists in map", nameof(key));
}
this[key] = value;
}
/// <summary>
/// Determines whether the specified key is present in the map.
/// </summary>
/// <param name="key">The key to check.</param>
/// <returns><c>true</c> if the map contains the given key; <c>false</c> otherwise.</returns>
public bool ContainsKey(TKey key)
{
ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
return map.ContainsKey(key);
}
private bool ContainsValue(TValue value) =>
list.Any(pair => ValueEqualityComparer.Equals(pair.Value, value));
/// <summary>
/// Removes the entry identified by the given key from the map.
/// </summary>
/// <param name="key">The key indicating the entry to remove from the map.</param>
/// <returns><c>true</c> if the map contained the given key before the entry was removed; <c>false</c> otherwise.</returns>
public bool Remove(TKey key)
{
ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
LinkedListNode<KeyValuePair<TKey, TValue>> node;
if (map.TryGetValue(key, out node))
{
map.Remove(key);
node.List.Remove(node);
return true;
}
else
{
return false;
}
}
/// <summary>
/// Gets the value associated with the specified key.
/// </summary>
/// <param name="key">The key whose value to get.</param>
/// <param name="value">When this method returns, the value associated with the specified key, if the key is found;
/// otherwise, the default value for the type of the <paramref name="value"/> parameter.
/// This parameter is passed uninitialized.</param>
/// <returns><c>true</c> if the map contains an element with the specified key; otherwise, <c>false</c>.</returns>
public bool TryGetValue(TKey key, out TValue value)
{
LinkedListNode<KeyValuePair<TKey, TValue>> node;
if (map.TryGetValue(key, out node))
{
value = node.Value.Value;
return true;
}
else
{
value = default(TValue);
return false;
}
}
/// <summary>
/// Gets or sets the value associated with the specified key.
/// </summary>
/// <param name="key">The key of the value to get or set.</param>
/// <exception cref="KeyNotFoundException">The property is retrieved and key does not exist in the collection.</exception>
/// <returns>The value associated with the specified key. If the specified key is not found,
/// a get operation throws a <see cref="KeyNotFoundException"/>, and a set operation creates a new element with the specified key.</returns>
public TValue this[TKey key]
{
get
{
ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
TValue value;
if (TryGetValue(key, out value))
{
return value;
}
throw new KeyNotFoundException();
}
set
{
ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
// value == null check here is redundant, but avoids boxing.
if (value == null)
{
ProtoPreconditions.CheckNotNullUnconstrained(value, nameof(value));
}
LinkedListNode<KeyValuePair<TKey, TValue>> node;
var pair = new KeyValuePair<TKey, TValue>(key, value);
if (map.TryGetValue(key, out node))
{
node.Value = pair;
}
else
{
node = list.AddLast(pair);
map[key] = node;
}
}
}
/// <summary>
/// Gets a collection containing the keys in the map.
/// </summary>
public ICollection<TKey> Keys { get { return new MapView<TKey>(this, pair => pair.Key, ContainsKey); } }
/// <summary>
/// Gets a collection containing the values in the map.
/// </summary>
public ICollection<TValue> Values { get { return new MapView<TValue>(this, pair => pair.Value, ContainsValue); } }
/// <summary>
/// Adds the specified entries to the map. The keys and values are not automatically cloned.
/// </summary>
/// <param name="entries">The entries to add to the map.</param>
public void Add(IDictionary<TKey, TValue> entries)
{
ProtoPreconditions.CheckNotNull(entries, nameof(entries));
foreach (var pair in entries)
{
Add(pair.Key, pair.Value);
}
}
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// An enumerator that can be used to iterate through the collection.
/// </returns>
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return list.GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
/// </returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// Adds the specified item to the map.
/// </summary>
/// <param name="item">The item to add to the map.</param>
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
{
Add(item.Key, item.Value);
}
/// <summary>
/// Removes all items from the map.
/// </summary>
public void Clear()
{
list.Clear();
map.Clear();
}
/// <summary>
/// Determines whether map contains an entry equivalent to the given key/value pair.
/// </summary>
/// <param name="item">The key/value pair to find.</param>
/// <returns></returns>
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
{
TValue value;
return TryGetValue(item.Key, out value) && ValueEqualityComparer.Equals(item.Value, value);
}
/// <summary>
/// Copies the key/value pairs in this map to an array.
/// </summary>
/// <param name="array">The array to copy the entries into.</param>
/// <param name="arrayIndex">The index of the array at which to start copying values.</param>
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
list.CopyTo(array, arrayIndex);
}
/// <summary>
/// Removes the specified key/value pair from the map.
/// </summary>
/// <remarks>Both the key and the value must be found for the entry to be removed.</remarks>
/// <param name="item">The key/value pair to remove.</param>
/// <returns><c>true</c> if the key/value pair was found and removed; <c>false</c> otherwise.</returns>
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
{
if (item.Key == null)
{
throw new ArgumentException("Key is null", nameof(item));
}
LinkedListNode<KeyValuePair<TKey, TValue>> node;
if (map.TryGetValue(item.Key, out node) &&
EqualityComparer<TValue>.Default.Equals(item.Value, node.Value.Value))
{
map.Remove(item.Key);
node.List.Remove(node);
return true;
}
else
{
return false;
}
}
/// <summary>
/// Gets the number of elements contained in the map.
/// </summary>
public int Count { get { return list.Count; } }
/// <summary>
/// Gets a value indicating whether the map is read-only.
/// </summary>
public bool IsReadOnly { get { return false; } }
/// <summary>
/// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
/// </summary>
/// <param name="other">The <see cref="System.Object" /> to compare with this instance.</param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object other)
{
return Equals(other as MapField<TKey, TValue>);
}
/// <summary>
/// Returns a hash code for this instance.
/// </summary>
/// <returns>
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// </returns>
public override int GetHashCode()
{
var keyComparer = KeyEqualityComparer;
var valueComparer = ValueEqualityComparer;
int hash = 0;
foreach (var pair in list)
{
hash ^= keyComparer.GetHashCode(pair.Key) * 31 + valueComparer.GetHashCode(pair.Value);
}
return hash;
}
/// <summary>
/// Compares this map with another for equality.
/// </summary>
/// <remarks>
/// The order of the key/value pairs in the maps is not deemed significant in this comparison.
/// </remarks>
/// <param name="other">The map to compare this with.</param>
/// <returns><c>true</c> if <paramref name="other"/> refers to an equal map; <c>false</c> otherwise.</returns>
public bool Equals(MapField<TKey, TValue> other)
{
if (other == null)
{
return false;
}
if (other == this)
{
return true;
}
if (other.Count != this.Count)
{
return false;
}
var valueComparer = ValueEqualityComparer;
foreach (var pair in this)
{
TValue value;
if (!other.TryGetValue(pair.Key, out value))
{
return false;
}
if (!valueComparer.Equals(value, pair.Value))
{
return false;
}
}
return true;
}
/// <summary>
/// Adds entries to the map from the given stream.
/// </summary>
/// <remarks>
/// It is assumed that the stream is initially positioned after the tag specified by the codec.
/// This method will continue reading entries from the stream until the end is reached, or
/// a different tag is encountered.
/// </remarks>
/// <param name="input">Stream to read from</param>
/// <param name="codec">Codec describing how the key/value pairs are encoded</param>
public void AddEntriesFrom(CodedInputStream input, Codec codec)
{
ParseContext.Initialize(input, out ParseContext ctx);
try
{
AddEntriesFrom(ref ctx, codec);
}
finally
{
ctx.CopyStateTo(input);
}
}
/// <summary>
/// Adds entries to the map from the given parse context.
/// </summary>
/// <remarks>
/// It is assumed that the input is initially positioned after the tag specified by the codec.
/// This method will continue reading entries from the input until the end is reached, or
/// a different tag is encountered.
/// </remarks>
/// <param name="ctx">Input to read from</param>
/// <param name="codec">Codec describing how the key/value pairs are encoded</param>
[SecuritySafeCritical]
public void AddEntriesFrom(ref ParseContext ctx, Codec codec)
{
var adapter = new Codec.MessageAdapter(codec);
do
{
adapter.Reset();
ctx.ReadMessage(adapter);
this[adapter.Key] = adapter.Value;
} while (ParsingPrimitives.MaybeConsumeTag(ref ctx.buffer, ref ctx.state, codec.MapTag));
}
/// <summary>
/// Writes the contents of this map to the given coded output stream, using the specified codec
/// to encode each entry.
/// </summary>
/// <param name="output">The output stream to write to.</param>
/// <param name="codec">The codec to use for each entry.</param>
public void WriteTo(CodedOutputStream output, Codec codec)
{
WriteContext.Initialize(output, out WriteContext ctx);
try
{
WriteTo(ref ctx, codec);
}
finally
{
ctx.CopyStateTo(output);
}
}
/// <summary>
/// Writes the contents of this map to the given write context, using the specified codec
/// to encode each entry.
/// </summary>
/// <param name="ctx">The write context to write to.</param>
/// <param name="codec">The codec to use for each entry.</param>
[SecuritySafeCritical]
public void WriteTo(ref WriteContext ctx, Codec codec)
{
var message = new Codec.MessageAdapter(codec);
foreach (var entry in list)
{
message.Key = entry.Key;
message.Value = entry.Value;
ctx.WriteTag(codec.MapTag);
ctx.WriteMessage(message);
}
}
/// <summary>
/// Calculates the size of this map based on the given entry codec.
/// </summary>
/// <param name="codec">The codec to use to encode each entry.</param>
/// <returns></returns>
public int CalculateSize(Codec codec)
{
if (Count == 0)
{
return 0;
}
var message = new Codec.MessageAdapter(codec);
int size = 0;
foreach (var entry in list)
{
message.Key = entry.Key;
message.Value = entry.Value;
size += CodedOutputStream.ComputeRawVarint32Size(codec.MapTag);
size += CodedOutputStream.ComputeMessageSize(message);
}
return size;
}
/// <summary>
/// Returns a string representation of this repeated field, in the same
/// way as it would be represented by the default JSON formatter.
/// </summary>
public override string ToString()
{
var writer = new StringWriter();
JsonFormatter.Default.WriteDictionary(writer, this);
return writer.ToString();
}
#region IDictionary explicit interface implementation
void IDictionary.Add(object key, object value)
{
Add((TKey)key, (TValue)value);
}
bool IDictionary.Contains(object key)
{
if (!(key is TKey))
{
return false;
}
return ContainsKey((TKey)key);
}
IDictionaryEnumerator IDictionary.GetEnumerator()
{
return new DictionaryEnumerator(GetEnumerator());
}
void IDictionary.Remove(object key)
{
ProtoPreconditions.CheckNotNull(key, nameof(key));
if (!(key is TKey))
{
return;
}
Remove((TKey)key);
}
void ICollection.CopyTo(Array array, int index)
{
// This is ugly and slow as heck, but with any luck it will never be used anyway.
ICollection temp = this.Select(pair => new DictionaryEntry(pair.Key, pair.Value)).ToList();
temp.CopyTo(array, index);
}
bool IDictionary.IsFixedSize { get { return false; } }
ICollection IDictionary.Keys { get { return (ICollection)Keys; } }
ICollection IDictionary.Values { get { return (ICollection)Values; } }
bool ICollection.IsSynchronized { get { return false; } }
object ICollection.SyncRoot { get { return this; } }
object IDictionary.this[object key]
{
get
{
ProtoPreconditions.CheckNotNull(key, nameof(key));
if (!(key is TKey))
{
return null;
}
TValue value;
TryGetValue((TKey)key, out value);
return value;
}
set
{
this[(TKey)key] = (TValue)value;
}
}
#endregion
#region IReadOnlyDictionary explicit interface implementation
#if !NET35
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => Keys;
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values;
#endif
#endregion
private class DictionaryEnumerator : IDictionaryEnumerator
{
private readonly IEnumerator<KeyValuePair<TKey, TValue>> enumerator;
internal DictionaryEnumerator(IEnumerator<KeyValuePair<TKey, TValue>> enumerator)
{
this.enumerator = enumerator;
}
public bool MoveNext()
{
return enumerator.MoveNext();
}
public void Reset()
{
enumerator.Reset();
}
public object Current { get { return Entry; } }
public DictionaryEntry Entry { get { return new DictionaryEntry(Key, Value); } }
public object Key { get { return enumerator.Current.Key; } }
public object Value { get { return enumerator.Current.Value; } }
}
/// <summary>
/// A codec for a specific map field. This contains all the information required to encode and
/// decode the nested messages.
/// </summary>
public sealed class Codec
{
private readonly FieldCodec<TKey> keyCodec;
private readonly FieldCodec<TValue> valueCodec;
private readonly uint mapTag;
/// <summary>
/// Creates a new entry codec based on a separate key codec and value codec,
/// and the tag to use for each map entry.
/// </summary>
/// <param name="keyCodec">The key codec.</param>
/// <param name="valueCodec">The value codec.</param>
/// <param name="mapTag">The map tag to use to introduce each map entry.</param>
public Codec(FieldCodec<TKey> keyCodec, FieldCodec<TValue> valueCodec, uint mapTag)
{
this.keyCodec = keyCodec;
this.valueCodec = valueCodec;
this.mapTag = mapTag;
}
/// <summary>
/// The tag used in the enclosing message to indicate map entries.
/// </summary>
internal uint MapTag { get { return mapTag; } }
/// <summary>
/// A mutable message class, used for parsing and serializing. This
/// delegates the work to a codec, but implements the <see cref="IMessage"/> interface
/// for interop with <see cref="CodedInputStream"/> and <see cref="CodedOutputStream"/>.
/// This is nested inside Codec as it's tightly coupled to the associated codec,
/// and it's simpler if it has direct access to all its fields.
/// </summary>
internal class MessageAdapter : IMessage, IBufferMessage
{
private static readonly byte[] ZeroLengthMessageStreamData = new byte[] { 0 };
private readonly Codec codec;
internal TKey Key { get; set; }
internal TValue Value { get; set; }
internal MessageAdapter(Codec codec)
{
this.codec = codec;
}
internal void Reset()
{
Key = codec.keyCodec.DefaultValue;
Value = codec.valueCodec.DefaultValue;
}
public void MergeFrom(CodedInputStream input)
{
// Message adapter is an internal class and we know that all the parsing will happen via InternalMergeFrom.
throw new NotImplementedException();
}
[SecuritySafeCritical]
public void InternalMergeFrom(ref ParseContext ctx)
{
uint tag;
while ((tag = ctx.ReadTag()) != 0)
{
if (tag == codec.keyCodec.Tag)
{
Key = codec.keyCodec.Read(ref ctx);
}
else if (tag == codec.valueCodec.Tag)
{
Value = codec.valueCodec.Read(ref ctx);
}
else
{
ParsingPrimitivesMessages.SkipLastField(ref ctx.buffer, ref ctx.state);
}
}
// Corner case: a map entry with a key but no value, where the value type is a message.
// Read it as if we'd seen input with no data (i.e. create a "default" message).
if (Value == null)
{
if (ctx.state.CodedInputStream != null)
{
// the decoded message might not support parsing from ParseContext, so
// we need to allow fallback to the legacy MergeFrom(CodedInputStream) parsing.
Value = codec.valueCodec.Read(new CodedInputStream(ZeroLengthMessageStreamData));
}
else
{
ParseContext.Initialize(new ReadOnlySequence<byte>(ZeroLengthMessageStreamData), out ParseContext zeroLengthCtx);
Value = codec.valueCodec.Read(ref zeroLengthCtx);
}
}
}
public void WriteTo(CodedOutputStream output)
{
// Message adapter is an internal class and we know that all the writing will happen via InternalWriteTo.
throw new NotImplementedException();
}
[SecuritySafeCritical]
public void InternalWriteTo(ref WriteContext ctx)
{
codec.keyCodec.WriteTagAndValue(ref ctx, Key);
codec.valueCodec.WriteTagAndValue(ref ctx, Value);
}
public int CalculateSize()
{
return codec.keyCodec.CalculateSizeWithTag(Key) + codec.valueCodec.CalculateSizeWithTag(Value);
}
MessageDescriptor IMessage.Descriptor { get { return null; } }
}
}
private class MapView<T> : ICollection<T>, ICollection
{
private readonly MapField<TKey, TValue> parent;
private readonly Func<KeyValuePair<TKey, TValue>, T> projection;
private readonly Func<T, bool> containsCheck;
internal MapView(
MapField<TKey, TValue> parent,
Func<KeyValuePair<TKey, TValue>, T> projection,
Func<T, bool> containsCheck)
{
this.parent = parent;
this.projection = projection;
this.containsCheck = containsCheck;
}
public int Count { get { return parent.Count; } }
public bool IsReadOnly { get { return true; } }
public bool IsSynchronized { get { return false; } }
public object SyncRoot { get { return parent; } }
public void Add(T item)
{
throw new NotSupportedException();
}
public void Clear()
{
throw new NotSupportedException();
}
public bool Contains(T item)
{
return containsCheck(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
if (arrayIndex < 0)
{
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
}
if (arrayIndex + Count > array.Length)
{
throw new ArgumentException("Not enough space in the array", nameof(array));
}
foreach (var item in this)
{
array[arrayIndex++] = item;
}
}
public IEnumerator<T> GetEnumerator()
{
return parent.list.Select(projection).GetEnumerator();
}
public bool Remove(T item)
{
throw new NotSupportedException();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void CopyTo(Array array, int index)
{
if (index < 0)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
if (index + Count > array.Length)
{
throw new ArgumentException("Not enough space in the array", nameof(array));
}
foreach (var item in this)
{
array.SetValue(item, index++);
}
}
}
}
}

View File

@ -0,0 +1,130 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2017 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Collections.Generic;
namespace LC.Google.Protobuf.Collections
{
/// <summary>
/// Provides a central place to implement equality comparisons, primarily for bitwise float/double equality.
/// </summary>
public static class ProtobufEqualityComparers
{
/// <summary>
/// Returns an equality comparer for <typeparamref name="T"/> suitable for Protobuf equality comparisons.
/// This is usually just the default equality comparer for the type, but floating point numbers are compared
/// bitwise.
/// </summary>
/// <typeparam name="T">The type of equality comparer to return.</typeparam>
/// <returns>The equality comparer.</returns>
public static EqualityComparer<T> GetEqualityComparer<T>()
{
return typeof(T) == typeof(double) ? (EqualityComparer<T>) (object) BitwiseDoubleEqualityComparer
: typeof(T) == typeof(float) ? (EqualityComparer<T>) (object) BitwiseSingleEqualityComparer
: typeof(T) == typeof(double?) ? (EqualityComparer<T>) (object) BitwiseNullableDoubleEqualityComparer
: typeof(T) == typeof(float?) ? (EqualityComparer<T>) (object) BitwiseNullableSingleEqualityComparer
: EqualityComparer<T>.Default;
}
/// <summary>
/// Returns an equality comparer suitable for comparing 64-bit floating point values, by bitwise comparison.
/// (NaN values are considered equal, but only when they have the same representation.)
/// </summary>
public static EqualityComparer<double> BitwiseDoubleEqualityComparer { get; } = new BitwiseDoubleEqualityComparerImpl();
/// <summary>
/// Returns an equality comparer suitable for comparing 32-bit floating point values, by bitwise comparison.
/// (NaN values are considered equal, but only when they have the same representation.)
/// </summary>
public static EqualityComparer<float> BitwiseSingleEqualityComparer { get; } = new BitwiseSingleEqualityComparerImpl();
/// <summary>
/// Returns an equality comparer suitable for comparing nullable 64-bit floating point values, by bitwise comparison.
/// (NaN values are considered equal, but only when they have the same representation.)
/// </summary>
public static EqualityComparer<double?> BitwiseNullableDoubleEqualityComparer { get; } = new BitwiseNullableDoubleEqualityComparerImpl();
/// <summary>
/// Returns an equality comparer suitable for comparing nullable 32-bit floating point values, by bitwise comparison.
/// (NaN values are considered equal, but only when they have the same representation.)
/// </summary>
public static EqualityComparer<float?> BitwiseNullableSingleEqualityComparer { get; } = new BitwiseNullableSingleEqualityComparerImpl();
private class BitwiseDoubleEqualityComparerImpl : EqualityComparer<double>
{
public override bool Equals(double x, double y) =>
BitConverter.DoubleToInt64Bits(x) == BitConverter.DoubleToInt64Bits(y);
public override int GetHashCode(double obj) =>
BitConverter.DoubleToInt64Bits(obj).GetHashCode();
}
private class BitwiseSingleEqualityComparerImpl : EqualityComparer<float>
{
// Just promote values to double and use BitConverter.DoubleToInt64Bits,
// as there's no BitConverter.SingleToInt32Bits, unfortunately.
public override bool Equals(float x, float y) =>
BitConverter.DoubleToInt64Bits(x) == BitConverter.DoubleToInt64Bits(y);
public override int GetHashCode(float obj) =>
BitConverter.DoubleToInt64Bits(obj).GetHashCode();
}
private class BitwiseNullableDoubleEqualityComparerImpl : EqualityComparer<double?>
{
public override bool Equals(double? x, double? y) =>
x == null && y == null ? true
: x == null || y == null ? false
: BitwiseDoubleEqualityComparer.Equals(x.Value, y.Value);
// The hash code for null is just a constant which is at least *unlikely* to be used
// elsewhere. (Compared with 0, say.)
public override int GetHashCode(double? obj) =>
obj == null ? 293864 : BitwiseDoubleEqualityComparer.GetHashCode(obj.Value);
}
private class BitwiseNullableSingleEqualityComparerImpl : EqualityComparer<float?>
{
public override bool Equals(float? x, float? y) =>
x == null && y == null ? true
: x == null || y == null ? false
: BitwiseSingleEqualityComparer.Equals(x.Value, y.Value);
// The hash code for null is just a constant which is at least *unlikely* to be used
// elsewhere. (Compared with 0, say.)
public override int GetHashCode(float? obj) =>
obj == null ? 293864 : BitwiseSingleEqualityComparer.GetHashCode(obj.Value);
}
}
}

View File

@ -0,0 +1,147 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
namespace LC.Google.Protobuf.Collections
{
/// <summary>
/// Read-only wrapper around another dictionary.
/// </summary>
internal sealed class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
private readonly IDictionary<TKey, TValue> wrapped;
public ReadOnlyDictionary(IDictionary<TKey, TValue> wrapped)
{
this.wrapped = wrapped;
}
public void Add(TKey key, TValue value)
{
throw new InvalidOperationException();
}
public bool ContainsKey(TKey key)
{
return wrapped.ContainsKey(key);
}
public ICollection<TKey> Keys
{
get { return wrapped.Keys; }
}
public bool Remove(TKey key)
{
throw new InvalidOperationException();
}
public bool TryGetValue(TKey key, out TValue value)
{
return wrapped.TryGetValue(key, out value);
}
public ICollection<TValue> Values
{
get { return wrapped.Values; }
}
public TValue this[TKey key]
{
get { return wrapped[key]; }
set { throw new InvalidOperationException(); }
}
public void Add(KeyValuePair<TKey, TValue> item)
{
throw new InvalidOperationException();
}
public void Clear()
{
throw new InvalidOperationException();
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return wrapped.Contains(item);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
wrapped.CopyTo(array, arrayIndex);
}
public int Count
{
get { return wrapped.Count; }
}
public bool IsReadOnly
{
get { return true; }
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
throw new InvalidOperationException();
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return wrapped.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable) wrapped).GetEnumerator();
}
public override bool Equals(object obj)
{
return wrapped.Equals(obj);
}
public override int GetHashCode()
{
return wrapped.GetHashCode();
}
public override string ToString()
{
return wrapped.ToString();
}
}
}

View File

@ -0,0 +1,698 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Security;
using System.Threading;
namespace LC.Google.Protobuf.Collections
{
/// <summary>
/// The contents of a repeated field: essentially, a collection with some extra
/// restrictions (no null values) and capabilities (deep cloning).
/// </summary>
/// <remarks>
/// This implementation does not generally prohibit the use of types which are not
/// supported by Protocol Buffers but nor does it guarantee that all operations will work in such cases.
/// </remarks>
/// <typeparam name="T">The element type of the repeated field.</typeparam>
public sealed class RepeatedField<T> : IList<T>, IList, IDeepCloneable<RepeatedField<T>>, IEquatable<RepeatedField<T>>
#if !NET35
, IReadOnlyList<T>
#endif
{
private static readonly EqualityComparer<T> EqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<T>();
private static readonly T[] EmptyArray = new T[0];
private const int MinArraySize = 8;
private T[] array = EmptyArray;
private int count = 0;
/// <summary>
/// Creates a deep clone of this repeated field.
/// </summary>
/// <remarks>
/// If the field type is
/// a message type, each element is also cloned; otherwise, it is
/// assumed that the field type is primitive (including string and
/// bytes, both of which are immutable) and so a simple copy is
/// equivalent to a deep clone.
/// </remarks>
/// <returns>A deep clone of this repeated field.</returns>
public RepeatedField<T> Clone()
{
RepeatedField<T> clone = new RepeatedField<T>();
if (array != EmptyArray)
{
clone.array = (T[])array.Clone();
IDeepCloneable<T>[] cloneableArray = clone.array as IDeepCloneable<T>[];
if (cloneableArray != null)
{
for (int i = 0; i < count; i++)
{
clone.array[i] = cloneableArray[i].Clone();
}
}
}
clone.count = count;
return clone;
}
/// <summary>
/// Adds the entries from the given input stream, decoding them with the specified codec.
/// </summary>
/// <param name="input">The input stream to read from.</param>
/// <param name="codec">The codec to use in order to read each entry.</param>
public void AddEntriesFrom(CodedInputStream input, FieldCodec<T> codec)
{
ParseContext.Initialize(input, out ParseContext ctx);
try
{
AddEntriesFrom(ref ctx, codec);
}
finally
{
ctx.CopyStateTo(input);
}
}
/// <summary>
/// Adds the entries from the given parse context, decoding them with the specified codec.
/// </summary>
/// <param name="ctx">The input to read from.</param>
/// <param name="codec">The codec to use in order to read each entry.</param>
[SecuritySafeCritical]
public void AddEntriesFrom(ref ParseContext ctx, FieldCodec<T> codec)
{
// TODO: Inline some of the Add code, so we can avoid checking the size on every
// iteration.
uint tag = ctx.state.lastTag;
var reader = codec.ValueReader;
// Non-nullable value types can be packed or not.
if (FieldCodec<T>.IsPackedRepeatedField(tag))
{
int length = ctx.ReadLength();
if (length > 0)
{
int oldLimit = SegmentedBufferHelper.PushLimit(ref ctx.state, length);
// If the content is fixed size then we can calculate the length
// of the repeated field and pre-initialize the underlying collection.
//
// Check that the supplied length doesn't exceed the underlying buffer.
// That prevents a malicious length from initializing a very large collection.
if (codec.FixedSize > 0 && length % codec.FixedSize == 0 && ParsingPrimitives.IsDataAvailable(ref ctx.state, length))
{
EnsureSize(count + (length / codec.FixedSize));
while (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state))
{
// Only FieldCodecs with a fixed size can reach here, and they are all known
// types that don't allow the user to specify a custom reader action.
// reader action will never return null.
array[count++] = reader(ref ctx);
}
}
else
{
// Content is variable size so add until we reach the limit.
while (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state))
{
Add(reader(ref ctx));
}
}
SegmentedBufferHelper.PopLimit(ref ctx.state, oldLimit);
}
// Empty packed field. Odd, but valid - just ignore.
}
else
{
// Not packed... (possibly not packable)
do
{
Add(reader(ref ctx));
} while (ParsingPrimitives.MaybeConsumeTag(ref ctx.buffer, ref ctx.state, tag));
}
}
/// <summary>
/// Calculates the size of this collection based on the given codec.
/// </summary>
/// <param name="codec">The codec to use when encoding each field.</param>
/// <returns>The number of bytes that would be written to an output by one of the <c>WriteTo</c> methods,
/// using the same codec.</returns>
public int CalculateSize(FieldCodec<T> codec)
{
if (count == 0)
{
return 0;
}
uint tag = codec.Tag;
if (codec.PackedRepeatedField)
{
int dataSize = CalculatePackedDataSize(codec);
return CodedOutputStream.ComputeRawVarint32Size(tag) +
CodedOutputStream.ComputeLengthSize(dataSize) +
dataSize;
}
else
{
var sizeCalculator = codec.ValueSizeCalculator;
int size = count * CodedOutputStream.ComputeRawVarint32Size(tag);
if (codec.EndTag != 0)
{
size += count * CodedOutputStream.ComputeRawVarint32Size(codec.EndTag);
}
for (int i = 0; i < count; i++)
{
size += sizeCalculator(array[i]);
}
return size;
}
}
private int CalculatePackedDataSize(FieldCodec<T> codec)
{
int fixedSize = codec.FixedSize;
if (fixedSize == 0)
{
var calculator = codec.ValueSizeCalculator;
int tmp = 0;
for (int i = 0; i < count; i++)
{
tmp += calculator(array[i]);
}
return tmp;
}
else
{
return fixedSize * Count;
}
}
/// <summary>
/// Writes the contents of this collection to the given <see cref="CodedOutputStream"/>,
/// encoding each value using the specified codec.
/// </summary>
/// <param name="output">The output stream to write to.</param>
/// <param name="codec">The codec to use when encoding each value.</param>
public void WriteTo(CodedOutputStream output, FieldCodec<T> codec)
{
WriteContext.Initialize(output, out WriteContext ctx);
try
{
WriteTo(ref ctx, codec);
}
finally
{
ctx.CopyStateTo(output);
}
}
/// <summary>
/// Writes the contents of this collection to the given write context,
/// encoding each value using the specified codec.
/// </summary>
/// <param name="ctx">The write context to write to.</param>
/// <param name="codec">The codec to use when encoding each value.</param>
[SecuritySafeCritical]
public void WriteTo(ref WriteContext ctx, FieldCodec<T> codec)
{
if (count == 0)
{
return;
}
var writer = codec.ValueWriter;
var tag = codec.Tag;
if (codec.PackedRepeatedField)
{
// Packed primitive type
int size = CalculatePackedDataSize(codec);
ctx.WriteTag(tag);
ctx.WriteLength(size);
for (int i = 0; i < count; i++)
{
writer(ref ctx, array[i]);
}
}
else
{
// Not packed: a simple tag/value pair for each value.
// Can't use codec.WriteTagAndValue, as that omits default values.
for (int i = 0; i < count; i++)
{
ctx.WriteTag(tag);
writer(ref ctx, array[i]);
if (codec.EndTag != 0)
{
ctx.WriteTag(codec.EndTag);
}
}
}
}
/// <summary>
/// Gets and sets the capacity of the RepeatedField's internal array. WHen set, the internal array is reallocated to the given capacity.
/// <exception cref="ArgumentOutOfRangeException">The new value is less than Count -or- when Count is less than 0.</exception>
/// </summary>
public int Capacity
{
get { return array.Length; }
set
{
if (value < count)
{
throw new ArgumentOutOfRangeException("Capacity", value,
$"Cannot set Capacity to a value smaller than the current item count, {count}");
}
if (value >= 0 && value != array.Length)
{
SetSize(value);
}
}
}
// May increase the size of the internal array, but will never shrink it.
private void EnsureSize(int size)
{
if (array.Length < size)
{
size = Math.Max(size, MinArraySize);
int newSize = Math.Max(array.Length * 2, size);
SetSize(newSize);
}
}
// Sets the internal array to an exact size.
private void SetSize(int size)
{
if (size != array.Length)
{
var tmp = new T[size];
Array.Copy(array, 0, tmp, 0, count);
array = tmp;
}
}
/// <summary>
/// Adds the specified item to the collection.
/// </summary>
/// <param name="item">The item to add.</param>
public void Add(T item)
{
ProtoPreconditions.CheckNotNullUnconstrained(item, nameof(item));
EnsureSize(count + 1);
array[count++] = item;
}
/// <summary>
/// Removes all items from the collection.
/// </summary>
public void Clear()
{
array = EmptyArray;
count = 0;
}
/// <summary>
/// Determines whether this collection contains the given item.
/// </summary>
/// <param name="item">The item to find.</param>
/// <returns><c>true</c> if this collection contains the given item; <c>false</c> otherwise.</returns>
public bool Contains(T item)
{
return IndexOf(item) != -1;
}
/// <summary>
/// Copies this collection to the given array.
/// </summary>
/// <param name="array">The array to copy to.</param>
/// <param name="arrayIndex">The first index of the array to copy to.</param>
public void CopyTo(T[] array, int arrayIndex)
{
Array.Copy(this.array, 0, array, arrayIndex, count);
}
/// <summary>
/// Removes the specified item from the collection
/// </summary>
/// <param name="item">The item to remove.</param>
/// <returns><c>true</c> if the item was found and removed; <c>false</c> otherwise.</returns>
public bool Remove(T item)
{
int index = IndexOf(item);
if (index == -1)
{
return false;
}
Array.Copy(array, index + 1, array, index, count - index - 1);
count--;
array[count] = default(T);
return true;
}
/// <summary>
/// Gets the number of elements contained in the collection.
/// </summary>
public int Count => count;
/// <summary>
/// Gets a value indicating whether the collection is read-only.
/// </summary>
public bool IsReadOnly => false;
/// <summary>
/// Adds all of the specified values into this collection.
/// </summary>
/// <param name="values">The values to add to this collection.</param>
public void AddRange(IEnumerable<T> values)
{
ProtoPreconditions.CheckNotNull(values, nameof(values));
// Optimization 1: If the collection we're adding is already a RepeatedField<T>,
// we know the values are valid.
var otherRepeatedField = values as RepeatedField<T>;
if (otherRepeatedField != null)
{
EnsureSize(count + otherRepeatedField.count);
Array.Copy(otherRepeatedField.array, 0, array, count, otherRepeatedField.count);
count += otherRepeatedField.count;
return;
}
// Optimization 2: The collection is an ICollection, so we can expand
// just once and ask the collection to copy itself into the array.
var collection = values as ICollection;
if (collection != null)
{
var extraCount = collection.Count;
// For reference types and nullable value types, we need to check that there are no nulls
// present. (This isn't a thread-safe approach, but we don't advertise this is thread-safe.)
// We expect the JITter to optimize this test to true/false, so it's effectively conditional
// specialization.
if (default(T) == null)
{
// TODO: Measure whether iterating once to check and then letting the collection copy
// itself is faster or slower than iterating and adding as we go. For large
// collections this will not be great in terms of cache usage... but the optimized
// copy may be significantly faster than doing it one at a time.
foreach (var item in collection)
{
if (item == null)
{
throw new ArgumentException("Sequence contained null element", nameof(values));
}
}
}
EnsureSize(count + extraCount);
collection.CopyTo(array, count);
count += extraCount;
return;
}
// We *could* check for ICollection<T> as well, but very very few collections implement
// ICollection<T> but not ICollection. (HashSet<T> does, for one...)
// Fall back to a slower path of adding items one at a time.
foreach (T item in values)
{
Add(item);
}
}
/// <summary>
/// Adds all of the specified values into this collection. This method is present to
/// allow repeated fields to be constructed from queries within collection initializers.
/// Within non-collection-initializer code, consider using the equivalent <see cref="AddRange"/>
/// method instead for clarity.
/// </summary>
/// <param name="values">The values to add to this collection.</param>
public void Add(IEnumerable<T> values)
{
AddRange(values);
}
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// An enumerator that can be used to iterate through the collection.
/// </returns>
public IEnumerator<T> GetEnumerator()
{
for (int i = 0; i < count; i++)
{
yield return array[i];
}
}
/// <summary>
/// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
/// </summary>
/// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object obj)
{
return Equals(obj as RepeatedField<T>);
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
/// </returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// Returns a hash code for this instance.
/// </summary>
/// <returns>
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// </returns>
public override int GetHashCode()
{
int hash = 0;
for (int i = 0; i < count; i++)
{
hash = hash * 31 + array[i].GetHashCode();
}
return hash;
}
/// <summary>
/// Compares this repeated field with another for equality.
/// </summary>
/// <param name="other">The repeated field to compare this with.</param>
/// <returns><c>true</c> if <paramref name="other"/> refers to an equal repeated field; <c>false</c> otherwise.</returns>
public bool Equals(RepeatedField<T> other)
{
if (ReferenceEquals(other, null))
{
return false;
}
if (ReferenceEquals(other, this))
{
return true;
}
if (other.Count != this.Count)
{
return false;
}
EqualityComparer<T> comparer = EqualityComparer;
for (int i = 0; i < count; i++)
{
if (!comparer.Equals(array[i], other.array[i]))
{
return false;
}
}
return true;
}
/// <summary>
/// Returns the index of the given item within the collection, or -1 if the item is not
/// present.
/// </summary>
/// <param name="item">The item to find in the collection.</param>
/// <returns>The zero-based index of the item, or -1 if it is not found.</returns>
public int IndexOf(T item)
{
ProtoPreconditions.CheckNotNullUnconstrained(item, nameof(item));
EqualityComparer<T> comparer = EqualityComparer;
for (int i = 0; i < count; i++)
{
if (comparer.Equals(array[i], item))
{
return i;
}
}
return -1;
}
/// <summary>
/// Inserts the given item at the specified index.
/// </summary>
/// <param name="index">The index at which to insert the item.</param>
/// <param name="item">The item to insert.</param>
public void Insert(int index, T item)
{
ProtoPreconditions.CheckNotNullUnconstrained(item, nameof(item));
if (index < 0 || index > count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
EnsureSize(count + 1);
Array.Copy(array, index, array, index + 1, count - index);
array[index] = item;
count++;
}
/// <summary>
/// Removes the item at the given index.
/// </summary>
/// <param name="index">The zero-based index of the item to remove.</param>
public void RemoveAt(int index)
{
if (index < 0 || index >= count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
Array.Copy(array, index + 1, array, index, count - index - 1);
count--;
array[count] = default(T);
}
/// <summary>
/// Returns a string representation of this repeated field, in the same
/// way as it would be represented by the default JSON formatter.
/// </summary>
public override string ToString()
{
var writer = new StringWriter();
JsonFormatter.Default.WriteList(writer, this);
return writer.ToString();
}
/// <summary>
/// Gets or sets the item at the specified index.
/// </summary>
/// <value>
/// The element at the specified index.
/// </value>
/// <param name="index">The zero-based index of the element to get or set.</param>
/// <returns>The item at the specified index.</returns>
public T this[int index]
{
get
{
if (index < 0 || index >= count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
return array[index];
}
set
{
if (index < 0 || index >= count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
ProtoPreconditions.CheckNotNullUnconstrained(value, nameof(value));
array[index] = value;
}
}
#region Explicit interface implementation for IList and ICollection.
bool IList.IsFixedSize => false;
void ICollection.CopyTo(Array array, int index)
{
Array.Copy(this.array, 0, array, index, count);
}
bool ICollection.IsSynchronized => false;
object ICollection.SyncRoot => this;
object IList.this[int index]
{
get { return this[index]; }
set { this[index] = (T)value; }
}
int IList.Add(object value)
{
Add((T) value);
return count - 1;
}
bool IList.Contains(object value)
{
return (value is T && Contains((T)value));
}
int IList.IndexOf(object value)
{
if (!(value is T))
{
return -1;
}
return IndexOf((T)value);
}
void IList.Insert(int index, object value)
{
Insert(index, (T) value);
}
void IList.Remove(object value)
{
if (!(value is T))
{
return;
}
Remove((T)value);
}
#endregion
}
}

View File

@ -0,0 +1,47 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2017 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
#if NET35
using System;
using System.Reflection;
namespace LC.Google.Protobuf.Compatibility
{
// .NET Core (at least netstandard1.0) doesn't have Delegate.CreateDelegate, and .NET 3.5 doesn't have
// MethodInfo.CreateDelegate. Proxy from one to the other on .NET 3.5...
internal static class MethodInfoExtensions
{
internal static Delegate CreateDelegate(this MethodInfo method, Type type) =>
Delegate.CreateDelegate(type, method);
}
}
#endif

View File

@ -0,0 +1,72 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System.Reflection;
namespace LC.Google.Protobuf.Compatibility
{
/// <summary>
/// Extension methods for <see cref="PropertyInfo"/>, effectively providing
/// the familiar members from previous desktop framework versions while
/// targeting the newer releases, .NET Core etc.
/// </summary>
internal static class PropertyInfoExtensions
{
/// <summary>
/// Returns the public getter of a property, or null if there is no such getter
/// (either because it's read-only, or the getter isn't public).
/// </summary>
internal static MethodInfo GetGetMethod(this PropertyInfo target)
{
#if NET35
var method = target.GetGetMethod();
#else
var method = target.GetMethod;
#endif
return method != null && method.IsPublic ? method : null;
}
/// <summary>
/// Returns the public setter of a property, or null if there is no such setter
/// (either because it's write-only, or the setter isn't public).
/// </summary>
internal static MethodInfo GetSetMethod(this PropertyInfo target)
{
#if NET35
var method = target.GetSetMethod();
#else
var method = target.SetMethod;
#endif
return method != null && method.IsPublic ? method : null;
}
}
}

View File

@ -0,0 +1,66 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
#if NET35
using System;
using System.IO;
namespace LC.Google.Protobuf.Compatibility
{
/// <summary>
/// Extension methods for <see cref="Stream"/> in order to provide
/// backwards compatibility with .NET 3.5
/// </summary>
public static class StreamExtensions
{
// 81920 seems to be the default buffer size used in .NET 4.5.1
private const int BUFFER_SIZE = 81920;
/// <summary>
/// Write the contents of the current stream to the destination stream
/// </summary>
public static void CopyTo(this Stream source, Stream destination)
{
if (destination == null)
{
throw new ArgumentNullException(nameof(destination));
}
byte[] buffer = new byte[BUFFER_SIZE];
int numBytesRead;
while ((numBytesRead = source.Read(buffer, 0, buffer.Length)) > 0) {
destination.Write(buffer, 0, numBytesRead);
}
}
}
}
#endif

View File

@ -0,0 +1,106 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Reflection;
#if !NET35
namespace LC.Google.Protobuf.Compatibility
{
/// <summary>
/// Provides extension methods on Type that just proxy to TypeInfo.
/// These are used to support the new type system from .NET 4.5, without
/// having calls to GetTypeInfo all over the place. While the methods here are meant to be
/// broadly compatible with the desktop framework, there are some subtle differences in behaviour - but
/// they're not expected to affect our use cases. While the class is internal, that should be fine: we can
/// evaluate each new use appropriately.
/// </summary>
internal static class TypeExtensions
{
/// <summary>
/// See https://msdn.microsoft.com/en-us/library/system.type.isassignablefrom
/// </summary>
internal static bool IsAssignableFrom(this Type target, Type c)
{
return target.GetTypeInfo().IsAssignableFrom(c.GetTypeInfo());
}
/// <summary>
/// Returns a representation of the public property associated with the given name in the given type,
/// including inherited properties or null if there is no such public property.
/// Here, "public property" means a property where either the getter, or the setter, or both, is public.
/// </summary>
internal static PropertyInfo GetProperty(this Type target, string name)
{
// GetDeclaredProperty only returns properties declared in the given type, so we need to recurse.
while (target != null)
{
var typeInfo = target.GetTypeInfo();
var ret = typeInfo.GetDeclaredProperty(name);
if (ret != null && ((ret.CanRead && ret.GetMethod.IsPublic) || (ret.CanWrite && ret.SetMethod.IsPublic)))
{
return ret;
}
target = typeInfo.BaseType;
}
return null;
}
/// <summary>
/// Returns a representation of the public method associated with the given name in the given type,
/// including inherited methods.
/// </summary>
/// <remarks>
/// This has a few differences compared with Type.GetMethod in the desktop framework. It will throw
/// if there is an ambiguous match even between a private method and a public one, but it *won't* throw
/// if there are two overloads at different levels in the type hierarchy (e.g. class Base declares public void Foo(int) and
/// class Child : Base declares public void Foo(long)).
/// </remarks>
/// <exception cref="AmbiguousMatchException">One type in the hierarchy declared more than one method with the same name</exception>
internal static MethodInfo GetMethod(this Type target, string name)
{
// GetDeclaredMethod only returns methods declared in the given type, so we need to recurse.
while (target != null)
{
var typeInfo = target.GetTypeInfo();
var ret = typeInfo.GetDeclaredMethod(name);
if (ret != null && ret.IsPublic)
{
return ret;
}
target = typeInfo.BaseType;
}
return null;
}
}
}
#endif

View File

@ -0,0 +1,119 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
namespace LC.Google.Protobuf
{
/// <summary>
/// Represents a non-generic extension definition. This API is experimental and subject to change.
/// </summary>
public abstract class Extension
{
internal abstract Type TargetType { get; }
/// <summary>
/// Internal use. Creates a new extension with the specified field number.
/// </summary>
protected Extension(int fieldNumber)
{
FieldNumber = fieldNumber;
}
internal abstract IExtensionValue CreateValue();
/// <summary>
/// Gets the field number of this extension
/// </summary>
public int FieldNumber { get; }
internal abstract bool IsRepeated { get; }
}
/// <summary>
/// Represents a type-safe extension identifier used for getting and setting single extension values in <see cref="IExtendableMessage{T}"/> instances.
/// This API is experimental and subject to change.
/// </summary>
/// <typeparam name="TTarget">The message type this field applies to</typeparam>
/// <typeparam name="TValue">The field value type of this extension</typeparam>
public sealed class Extension<TTarget, TValue> : Extension where TTarget : IExtendableMessage<TTarget>
{
private readonly FieldCodec<TValue> codec;
/// <summary>
/// Creates a new extension identifier with the specified field number and codec
/// </summary>
public Extension(int fieldNumber, FieldCodec<TValue> codec) : base(fieldNumber)
{
this.codec = codec;
}
internal TValue DefaultValue => codec.DefaultValue;
internal override Type TargetType => typeof(TTarget);
internal override bool IsRepeated => false;
internal override IExtensionValue CreateValue()
{
return new ExtensionValue<TValue>(codec);
}
}
/// <summary>
/// Represents a type-safe extension identifier used for getting repeated extension values in <see cref="IExtendableMessage{T}"/> instances.
/// This API is experimental and subject to change.
/// </summary>
/// <typeparam name="TTarget">The message type this field applies to</typeparam>
/// <typeparam name="TValue">The repeated field value type of this extension</typeparam>
public sealed class RepeatedExtension<TTarget, TValue> : Extension where TTarget : IExtendableMessage<TTarget>
{
private readonly FieldCodec<TValue> codec;
/// <summary>
/// Creates a new repeated extension identifier with the specified field number and codec
/// </summary>
public RepeatedExtension(int fieldNumber, FieldCodec<TValue> codec) : base(fieldNumber)
{
this.codec = codec;
}
internal override Type TargetType => typeof(TTarget);
internal override bool IsRepeated => true;
internal override IExtensionValue CreateValue()
{
return new RepeatedExtensionValue<TValue>(codec);
}
}
}

View File

@ -0,0 +1,184 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace LC.Google.Protobuf
{
/// <summary>
/// Provides extensions to messages while parsing. This API is experimental and subject to change.
/// </summary>
public sealed class ExtensionRegistry : ICollection<Extension>, IDeepCloneable<ExtensionRegistry>
{
internal sealed class ExtensionComparer : IEqualityComparer<Extension>
{
public bool Equals(Extension a, Extension b)
{
return new ObjectIntPair<Type>(a.TargetType, a.FieldNumber).Equals(new ObjectIntPair<Type>(b.TargetType, b.FieldNumber));
}
public int GetHashCode(Extension a)
{
return new ObjectIntPair<Type>(a.TargetType, a.FieldNumber).GetHashCode();
}
internal static ExtensionComparer Instance = new ExtensionComparer();
}
private IDictionary<ObjectIntPair<Type>, Extension> extensions;
/// <summary>
/// Creates a new empty extension registry
/// </summary>
public ExtensionRegistry()
{
extensions = new Dictionary<ObjectIntPair<Type>, Extension>();
}
private ExtensionRegistry(IDictionary<ObjectIntPair<Type>, Extension> collection)
{
extensions = collection.ToDictionary(k => k.Key, v => v.Value);
}
/// <summary>
/// Gets the total number of extensions in this extension registry
/// </summary>
public int Count => extensions.Count;
/// <summary>
/// Returns whether the registry is readonly
/// </summary>
bool ICollection<Extension>.IsReadOnly => false;
internal bool ContainsInputField(uint lastTag, Type target, out Extension extension)
{
return extensions.TryGetValue(new ObjectIntPair<Type>(target, WireFormat.GetTagFieldNumber(lastTag)), out extension);
}
/// <summary>
/// Adds the specified extension to the registry
/// </summary>
public void Add(Extension extension)
{
ProtoPreconditions.CheckNotNull(extension, nameof(extension));
extensions.Add(new ObjectIntPair<Type>(extension.TargetType, extension.FieldNumber), extension);
}
/// <summary>
/// Adds the specified extensions to the registry
/// </summary>
public void AddRange(IEnumerable<Extension> extensions)
{
ProtoPreconditions.CheckNotNull(extensions, nameof(extensions));
foreach (var extension in extensions)
{
Add(extension);
}
}
/// <summary>
/// Clears the registry of all values
/// </summary>
public void Clear()
{
extensions.Clear();
}
/// <summary>
/// Gets whether the extension registry contains the specified extension
/// </summary>
public bool Contains(Extension item)
{
ProtoPreconditions.CheckNotNull(item, nameof(item));
return extensions.ContainsKey(new ObjectIntPair<Type>(item.TargetType, item.FieldNumber));
}
/// <summary>
/// Copies the arrays in the registry set to the specified array at the specified index
/// </summary>
/// <param name="array">The array to copy to</param>
/// <param name="arrayIndex">The array index to start at</param>
void ICollection<Extension>.CopyTo(Extension[] array, int arrayIndex)
{
ProtoPreconditions.CheckNotNull(array, nameof(array));
if (arrayIndex < 0 || arrayIndex >= array.Length)
{
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
}
if (array.Length - arrayIndex < Count)
{
throw new ArgumentException("The provided array is shorter than the number of elements in the registry");
}
for (int i = 0; i < array.Length; i++)
{
Extension extension = array[i];
extensions.Add(new ObjectIntPair<Type>(extension.TargetType, extension.FieldNumber), extension);
}
}
/// <summary>
/// Returns an enumerator to enumerate through the items in the registry
/// </summary>
/// <returns>Returns an enumerator for the extensions in this registry</returns>
public IEnumerator<Extension> GetEnumerator()
{
return extensions.Values.GetEnumerator();
}
/// <summary>
/// Removes the specified extension from the set
/// </summary>
/// <param name="item">The extension</param>
/// <returns><c>true</c> if the extension was removed, otherwise <c>false</c></returns>
public bool Remove(Extension item)
{
ProtoPreconditions.CheckNotNull(item, nameof(item));
return extensions.Remove(new ObjectIntPair<Type>(item.TargetType, item.FieldNumber));
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Clones the registry into a new registry
/// </summary>
public ExtensionRegistry Clone()
{
return new ExtensionRegistry(extensions);
}
}
}

View File

@ -0,0 +1,377 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using LC.Google.Protobuf.Collections;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security;
namespace LC.Google.Protobuf
{
/// <summary>
/// Methods for managing <see cref="ExtensionSet{TTarget}"/>s with null checking.
///
/// Most users will not use this class directly and its API is experimental and subject to change.
/// </summary>
public static class ExtensionSet
{
private static bool TryGetValue<TTarget>(ref ExtensionSet<TTarget> set, Extension extension, out IExtensionValue value) where TTarget : IExtendableMessage<TTarget>
{
if (set == null)
{
value = null;
return false;
}
return set.ValuesByNumber.TryGetValue(extension.FieldNumber, out value);
}
/// <summary>
/// Gets the value of the specified extension
/// </summary>
public static TValue Get<TTarget, TValue>(ref ExtensionSet<TTarget> set, Extension<TTarget, TValue> extension) where TTarget : IExtendableMessage<TTarget>
{
IExtensionValue value;
if (TryGetValue(ref set, extension, out value))
{
return ((ExtensionValue<TValue>)value).GetValue();
}
else
{
return extension.DefaultValue;
}
}
/// <summary>
/// Gets the value of the specified repeated extension or null if it doesn't exist in this set
/// </summary>
public static RepeatedField<TValue> Get<TTarget, TValue>(ref ExtensionSet<TTarget> set, RepeatedExtension<TTarget, TValue> extension) where TTarget : IExtendableMessage<TTarget>
{
IExtensionValue value;
if (TryGetValue(ref set, extension, out value))
{
return ((RepeatedExtensionValue<TValue>)value).GetValue();
}
else
{
return null;
}
}
/// <summary>
/// Gets the value of the specified repeated extension, registering it if it doesn't exist
/// </summary>
public static RepeatedField<TValue> GetOrInitialize<TTarget, TValue>(ref ExtensionSet<TTarget> set, RepeatedExtension<TTarget, TValue> extension) where TTarget : IExtendableMessage<TTarget>
{
IExtensionValue value;
if (set == null)
{
value = extension.CreateValue();
set = new ExtensionSet<TTarget>();
set.ValuesByNumber.Add(extension.FieldNumber, value);
}
else
{
if (!set.ValuesByNumber.TryGetValue(extension.FieldNumber, out value))
{
value = extension.CreateValue();
set.ValuesByNumber.Add(extension.FieldNumber, value);
}
}
return ((RepeatedExtensionValue<TValue>)value).GetValue();
}
/// <summary>
/// Sets the value of the specified extension. This will make a new instance of ExtensionSet if the set is null.
/// </summary>
public static void Set<TTarget, TValue>(ref ExtensionSet<TTarget> set, Extension<TTarget, TValue> extension, TValue value) where TTarget : IExtendableMessage<TTarget>
{
ProtoPreconditions.CheckNotNullUnconstrained(value, nameof(value));
IExtensionValue extensionValue;
if (set == null)
{
extensionValue = extension.CreateValue();
set = new ExtensionSet<TTarget>();
set.ValuesByNumber.Add(extension.FieldNumber, extensionValue);
}
else
{
if (!set.ValuesByNumber.TryGetValue(extension.FieldNumber, out extensionValue))
{
extensionValue = extension.CreateValue();
set.ValuesByNumber.Add(extension.FieldNumber, extensionValue);
}
}
((ExtensionValue<TValue>)extensionValue).SetValue(value);
}
/// <summary>
/// Gets whether the value of the specified extension is set
/// </summary>
public static bool Has<TTarget, TValue>(ref ExtensionSet<TTarget> set, Extension<TTarget, TValue> extension) where TTarget : IExtendableMessage<TTarget>
{
IExtensionValue value;
return TryGetValue(ref set, extension, out value);
}
/// <summary>
/// Clears the value of the specified extension
/// </summary>
public static void Clear<TTarget, TValue>(ref ExtensionSet<TTarget> set, Extension<TTarget, TValue> extension) where TTarget : IExtendableMessage<TTarget>
{
if (set == null)
{
return;
}
set.ValuesByNumber.Remove(extension.FieldNumber);
if (set.ValuesByNumber.Count == 0)
{
set = null;
}
}
/// <summary>
/// Clears the value of the specified extension
/// </summary>
public static void Clear<TTarget, TValue>(ref ExtensionSet<TTarget> set, RepeatedExtension<TTarget, TValue> extension) where TTarget : IExtendableMessage<TTarget>
{
if (set == null)
{
return;
}
set.ValuesByNumber.Remove(extension.FieldNumber);
if (set.ValuesByNumber.Count == 0)
{
set = null;
}
}
/// <summary>
/// Tries to merge a field from the coded input, returning true if the field was merged.
/// If the set is null or the field was not otherwise merged, this returns false.
/// </summary>
public static bool TryMergeFieldFrom<TTarget>(ref ExtensionSet<TTarget> set, CodedInputStream stream) where TTarget : IExtendableMessage<TTarget>
{
ParseContext.Initialize(stream, out ParseContext ctx);
try
{
return TryMergeFieldFrom<TTarget>(ref set, ref ctx);
}
finally
{
ctx.CopyStateTo(stream);
}
}
/// <summary>
/// Tries to merge a field from the coded input, returning true if the field was merged.
/// If the set is null or the field was not otherwise merged, this returns false.
/// </summary>
public static bool TryMergeFieldFrom<TTarget>(ref ExtensionSet<TTarget> set, ref ParseContext ctx) where TTarget : IExtendableMessage<TTarget>
{
Extension extension;
int lastFieldNumber = WireFormat.GetTagFieldNumber(ctx.LastTag);
IExtensionValue extensionValue;
if (set != null && set.ValuesByNumber.TryGetValue(lastFieldNumber, out extensionValue))
{
extensionValue.MergeFrom(ref ctx);
return true;
}
else if (ctx.ExtensionRegistry != null && ctx.ExtensionRegistry.ContainsInputField(ctx.LastTag, typeof(TTarget), out extension))
{
IExtensionValue value = extension.CreateValue();
value.MergeFrom(ref ctx);
set = (set ?? new ExtensionSet<TTarget>());
set.ValuesByNumber.Add(extension.FieldNumber, value);
return true;
}
else
{
return false;
}
}
/// <summary>
/// Merges the second set into the first set, creating a new instance if first is null
/// </summary>
public static void MergeFrom<TTarget>(ref ExtensionSet<TTarget> first, ExtensionSet<TTarget> second) where TTarget : IExtendableMessage<TTarget>
{
if (second == null)
{
return;
}
if (first == null)
{
first = new ExtensionSet<TTarget>();
}
foreach (var pair in second.ValuesByNumber)
{
IExtensionValue value;
if (first.ValuesByNumber.TryGetValue(pair.Key, out value))
{
value.MergeFrom(pair.Value);
}
else
{
var cloned = pair.Value.Clone();
first.ValuesByNumber[pair.Key] = cloned;
}
}
}
/// <summary>
/// Clones the set into a new set. If the set is null, this returns null
/// </summary>
public static ExtensionSet<TTarget> Clone<TTarget>(ExtensionSet<TTarget> set) where TTarget : IExtendableMessage<TTarget>
{
if (set == null)
{
return null;
}
var newSet = new ExtensionSet<TTarget>();
foreach (var pair in set.ValuesByNumber)
{
var cloned = pair.Value.Clone();
newSet.ValuesByNumber[pair.Key] = cloned;
}
return newSet;
}
}
/// <summary>
/// Used for keeping track of extensions in messages.
/// <see cref="IExtendableMessage{T}"/> methods route to this set.
///
/// Most users will not need to use this class directly
/// </summary>
/// <typeparam name="TTarget">The message type that extensions in this set target</typeparam>
public sealed class ExtensionSet<TTarget> where TTarget : IExtendableMessage<TTarget>
{
internal Dictionary<int, IExtensionValue> ValuesByNumber { get; } = new Dictionary<int, IExtensionValue>();
/// <summary>
/// Gets a hash code of the set
/// </summary>
public override int GetHashCode()
{
int ret = typeof(TTarget).GetHashCode();
foreach (KeyValuePair<int, IExtensionValue> field in ValuesByNumber)
{
// Use ^ here to make the field order irrelevant.
int hash = field.Key.GetHashCode() ^ field.Value.GetHashCode();
ret ^= hash;
}
return ret;
}
/// <summary>
/// Returns whether this set is equal to the other object
/// </summary>
public override bool Equals(object other)
{
if (ReferenceEquals(this, other))
{
return true;
}
ExtensionSet<TTarget> otherSet = other as ExtensionSet<TTarget>;
if (ValuesByNumber.Count != otherSet.ValuesByNumber.Count)
{
return false;
}
foreach (var pair in ValuesByNumber)
{
IExtensionValue secondValue;
if (!otherSet.ValuesByNumber.TryGetValue(pair.Key, out secondValue))
{
return false;
}
if (!pair.Value.Equals(secondValue))
{
return false;
}
}
return true;
}
/// <summary>
/// Calculates the size of this extension set
/// </summary>
public int CalculateSize()
{
int size = 0;
foreach (var value in ValuesByNumber.Values)
{
size += value.CalculateSize();
}
return size;
}
/// <summary>
/// Writes the extension values in this set to the output stream
/// </summary>
public void WriteTo(CodedOutputStream stream)
{
WriteContext.Initialize(stream, out WriteContext ctx);
try
{
WriteTo(ref ctx);
}
finally
{
ctx.CopyStateTo(stream);
}
}
/// <summary>
/// Writes the extension values in this set to the write context
/// </summary>
[SecuritySafeCritical]
public void WriteTo(ref WriteContext ctx)
{
foreach (var value in ValuesByNumber.Values)
{
value.WriteTo(ref ctx);
}
}
internal bool IsInitialized()
{
return ValuesByNumber.Values.All(v => v.IsInitialized());
}
}
}

View File

@ -0,0 +1,225 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using LC.Google.Protobuf.Collections;
using System;
using System.Linq;
namespace LC.Google.Protobuf
{
internal interface IExtensionValue : IEquatable<IExtensionValue>, IDeepCloneable<IExtensionValue>
{
void MergeFrom(ref ParseContext ctx);
void MergeFrom(IExtensionValue value);
void WriteTo(ref WriteContext ctx);
int CalculateSize();
bool IsInitialized();
}
internal sealed class ExtensionValue<T> : IExtensionValue
{
private T field;
private FieldCodec<T> codec;
internal ExtensionValue(FieldCodec<T> codec)
{
this.codec = codec;
field = codec.DefaultValue;
}
public int CalculateSize()
{
return codec.CalculateSizeWithTag(field);
}
public IExtensionValue Clone()
{
return new ExtensionValue<T>(codec)
{
field = field is IDeepCloneable<T> ? (field as IDeepCloneable<T>).Clone() : field
};
}
public bool Equals(IExtensionValue other)
{
if (ReferenceEquals(this, other))
return true;
return other is ExtensionValue<T>
&& codec.Equals((other as ExtensionValue<T>).codec)
&& Equals(field, (other as ExtensionValue<T>).field);
// we check for equality in the codec since we could have equal field values however the values could be written in different ways
}
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = hash * 31 + field.GetHashCode();
hash = hash * 31 + codec.GetHashCode();
return hash;
}
}
public void MergeFrom(ref ParseContext ctx)
{
codec.ValueMerger(ref ctx, ref field);
}
public void MergeFrom(IExtensionValue value)
{
if (value is ExtensionValue<T>)
{
var extensionValue = value as ExtensionValue<T>;
codec.FieldMerger(ref field, extensionValue.field);
}
}
public void WriteTo(ref WriteContext ctx)
{
ctx.WriteTag(codec.Tag);
codec.ValueWriter(ref ctx, field);
if (codec.EndTag != 0)
{
ctx.WriteTag(codec.EndTag);
}
}
public T GetValue() => field;
public void SetValue(T value)
{
field = value;
}
public bool IsInitialized()
{
if (field is IMessage)
{
return (field as IMessage).IsInitialized();
}
else
{
return true;
}
}
}
internal sealed class RepeatedExtensionValue<T> : IExtensionValue
{
private RepeatedField<T> field;
private readonly FieldCodec<T> codec;
internal RepeatedExtensionValue(FieldCodec<T> codec)
{
this.codec = codec;
field = new RepeatedField<T>();
}
public int CalculateSize()
{
return field.CalculateSize(codec);
}
public IExtensionValue Clone()
{
return new RepeatedExtensionValue<T>(codec)
{
field = field.Clone()
};
}
public bool Equals(IExtensionValue other)
{
if (ReferenceEquals(this, other))
return true;
return other is RepeatedExtensionValue<T>
&& field.Equals((other as RepeatedExtensionValue<T>).field)
&& codec.Equals((other as RepeatedExtensionValue<T>).codec);
}
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = hash * 31 + field.GetHashCode();
hash = hash * 31 + codec.GetHashCode();
return hash;
}
}
public void MergeFrom(ref ParseContext ctx)
{
field.AddEntriesFrom(ref ctx, codec);
}
public void MergeFrom(IExtensionValue value)
{
if (value is RepeatedExtensionValue<T>)
{
field.Add((value as RepeatedExtensionValue<T>).field);
}
}
public void WriteTo(ref WriteContext ctx)
{
field.WriteTo(ref ctx, codec);
}
public RepeatedField<T> GetValue() => field;
public bool IsInitialized()
{
for (int i = 0; i < field.Count; i++)
{
var element = field[i];
if (element is IMessage)
{
if (!(element as IMessage).IsInitialized())
{
return false;
}
}
else
{
break;
}
}
return true;
}
}
}

View File

@ -0,0 +1,881 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using LC.Google.Protobuf.Collections;
using LC.Google.Protobuf.Compatibility;
using LC.Google.Protobuf.WellKnownTypes;
using System;
using System.Collections.Generic;
using System.Security;
namespace LC.Google.Protobuf
{
/// <summary>
/// Factory methods for <see cref="FieldCodec{T}"/>.
/// </summary>
public static class FieldCodec
{
// TODO: Avoid the "dual hit" of lambda expressions: create open delegates instead. (At least test...)
/// <summary>
/// Retrieves a codec suitable for a string field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<string> ForString(uint tag)
{
return FieldCodec.ForString(tag, "");
}
/// <summary>
/// Retrieves a codec suitable for a bytes field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<ByteString> ForBytes(uint tag)
{
return FieldCodec.ForBytes(tag, ByteString.Empty);
}
/// <summary>
/// Retrieves a codec suitable for a bool field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<bool> ForBool(uint tag)
{
return FieldCodec.ForBool(tag, false);
}
/// <summary>
/// Retrieves a codec suitable for an int32 field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<int> ForInt32(uint tag)
{
return FieldCodec.ForInt32(tag, 0);
}
/// <summary>
/// Retrieves a codec suitable for an sint32 field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<int> ForSInt32(uint tag)
{
return FieldCodec.ForSInt32(tag, 0);
}
/// <summary>
/// Retrieves a codec suitable for a fixed32 field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<uint> ForFixed32(uint tag)
{
return FieldCodec.ForFixed32(tag, 0);
}
/// <summary>
/// Retrieves a codec suitable for an sfixed32 field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<int> ForSFixed32(uint tag)
{
return FieldCodec.ForSFixed32(tag, 0);
}
/// <summary>
/// Retrieves a codec suitable for a uint32 field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<uint> ForUInt32(uint tag)
{
return FieldCodec.ForUInt32(tag, 0);
}
/// <summary>
/// Retrieves a codec suitable for an int64 field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<long> ForInt64(uint tag)
{
return FieldCodec.ForInt64(tag, 0);
}
/// <summary>
/// Retrieves a codec suitable for an sint64 field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<long> ForSInt64(uint tag)
{
return FieldCodec.ForSInt64(tag, 0);
}
/// <summary>
/// Retrieves a codec suitable for a fixed64 field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<ulong> ForFixed64(uint tag)
{
return FieldCodec.ForFixed64(tag, 0);
}
/// <summary>
/// Retrieves a codec suitable for an sfixed64 field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<long> ForSFixed64(uint tag)
{
return FieldCodec.ForSFixed64(tag, 0);
}
/// <summary>
/// Retrieves a codec suitable for a uint64 field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<ulong> ForUInt64(uint tag)
{
return FieldCodec.ForUInt64(tag, 0);
}
/// <summary>
/// Retrieves a codec suitable for a float field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<float> ForFloat(uint tag)
{
return FieldCodec.ForFloat(tag, 0);
}
/// <summary>
/// Retrieves a codec suitable for a double field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<double> ForDouble(uint tag)
{
return FieldCodec.ForDouble(tag, 0);
}
// Enums are tricky. We can probably use expression trees to build these delegates automatically,
// but it's easy to generate the code for it.
/// <summary>
/// Retrieves a codec suitable for an enum field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="toInt32">A conversion function from <see cref="Int32"/> to the enum type.</param>
/// <param name="fromInt32">A conversion function from the enum type to <see cref="Int32"/>.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<T> ForEnum<T>(uint tag, Func<T, int> toInt32, Func<int, T> fromInt32)
{
return FieldCodec.ForEnum(tag, toInt32, fromInt32, default(T));
}
/// <summary>
/// Retrieves a codec suitable for a string field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<string> ForString(uint tag, string defaultValue)
{
return new FieldCodec<string>((ref ParseContext ctx) => ctx.ReadString(), (ref WriteContext ctx, string value) => ctx.WriteString(value), CodedOutputStream.ComputeStringSize, tag, defaultValue);
}
/// <summary>
/// Retrieves a codec suitable for a bytes field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<ByteString> ForBytes(uint tag, ByteString defaultValue)
{
return new FieldCodec<ByteString>((ref ParseContext ctx) => ctx.ReadBytes(), (ref WriteContext ctx, ByteString value) => ctx.WriteBytes(value), CodedOutputStream.ComputeBytesSize, tag, defaultValue);
}
/// <summary>
/// Retrieves a codec suitable for a bool field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<bool> ForBool(uint tag, bool defaultValue)
{
return new FieldCodec<bool>((ref ParseContext ctx) => ctx.ReadBool(), (ref WriteContext ctx, bool value) => ctx.WriteBool(value), CodedOutputStream.BoolSize, tag, defaultValue);
}
/// <summary>
/// Retrieves a codec suitable for an int32 field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<int> ForInt32(uint tag, int defaultValue)
{
return new FieldCodec<int>((ref ParseContext ctx) => ctx.ReadInt32(), (ref WriteContext output, int value) => output.WriteInt32(value), CodedOutputStream.ComputeInt32Size, tag, defaultValue);
}
/// <summary>
/// Retrieves a codec suitable for an sint32 field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<int> ForSInt32(uint tag, int defaultValue)
{
return new FieldCodec<int>((ref ParseContext ctx) => ctx.ReadSInt32(), (ref WriteContext output, int value) => output.WriteSInt32(value), CodedOutputStream.ComputeSInt32Size, tag, defaultValue);
}
/// <summary>
/// Retrieves a codec suitable for a fixed32 field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<uint> ForFixed32(uint tag, uint defaultValue)
{
return new FieldCodec<uint>((ref ParseContext ctx) => ctx.ReadFixed32(), (ref WriteContext output, uint value) => output.WriteFixed32(value), 4, tag, defaultValue);
}
/// <summary>
/// Retrieves a codec suitable for an sfixed32 field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<int> ForSFixed32(uint tag, int defaultValue)
{
return new FieldCodec<int>((ref ParseContext ctx) => ctx.ReadSFixed32(), (ref WriteContext output, int value) => output.WriteSFixed32(value), 4, tag, defaultValue);
}
/// <summary>
/// Retrieves a codec suitable for a uint32 field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<uint> ForUInt32(uint tag, uint defaultValue)
{
return new FieldCodec<uint>((ref ParseContext ctx) => ctx.ReadUInt32(), (ref WriteContext output, uint value) => output.WriteUInt32(value), CodedOutputStream.ComputeUInt32Size, tag, defaultValue);
}
/// <summary>
/// Retrieves a codec suitable for an int64 field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<long> ForInt64(uint tag, long defaultValue)
{
return new FieldCodec<long>((ref ParseContext ctx) => ctx.ReadInt64(), (ref WriteContext output, long value) => output.WriteInt64(value), CodedOutputStream.ComputeInt64Size, tag, defaultValue);
}
/// <summary>
/// Retrieves a codec suitable for an sint64 field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<long> ForSInt64(uint tag, long defaultValue)
{
return new FieldCodec<long>((ref ParseContext ctx) => ctx.ReadSInt64(), (ref WriteContext output, long value) => output.WriteSInt64(value), CodedOutputStream.ComputeSInt64Size, tag, defaultValue);
}
/// <summary>
/// Retrieves a codec suitable for a fixed64 field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<ulong> ForFixed64(uint tag, ulong defaultValue)
{
return new FieldCodec<ulong>((ref ParseContext ctx) => ctx.ReadFixed64(), (ref WriteContext output, ulong value) => output.WriteFixed64(value), 8, tag, defaultValue);
}
/// <summary>
/// Retrieves a codec suitable for an sfixed64 field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<long> ForSFixed64(uint tag, long defaultValue)
{
return new FieldCodec<long>((ref ParseContext ctx) => ctx.ReadSFixed64(), (ref WriteContext output, long value) => output.WriteSFixed64(value), 8, tag, defaultValue);
}
/// <summary>
/// Retrieves a codec suitable for a uint64 field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<ulong> ForUInt64(uint tag, ulong defaultValue)
{
return new FieldCodec<ulong>((ref ParseContext ctx) => ctx.ReadUInt64(), (ref WriteContext output, ulong value) => output.WriteUInt64(value), CodedOutputStream.ComputeUInt64Size, tag, defaultValue);
}
/// <summary>
/// Retrieves a codec suitable for a float field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<float> ForFloat(uint tag, float defaultValue)
{
return new FieldCodec<float>((ref ParseContext ctx) => ctx.ReadFloat(), (ref WriteContext output, float value) => output.WriteFloat(value), CodedOutputStream.FloatSize, tag, defaultValue);
}
/// <summary>
/// Retrieves a codec suitable for a double field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<double> ForDouble(uint tag, double defaultValue)
{
return new FieldCodec<double>((ref ParseContext ctx) => ctx.ReadDouble(), (ref WriteContext output, double value) => output.WriteDouble(value), CodedOutputStream.DoubleSize, tag, defaultValue);
}
// Enums are tricky. We can probably use expression trees to build these delegates automatically,
// but it's easy to generate the code for it.
/// <summary>
/// Retrieves a codec suitable for an enum field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="toInt32">A conversion function from <see cref="Int32"/> to the enum type.</param>
/// <param name="fromInt32">A conversion function from the enum type to <see cref="Int32"/>.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<T> ForEnum<T>(uint tag, Func<T, int> toInt32, Func<int, T> fromInt32, T defaultValue)
{
return new FieldCodec<T>((ref ParseContext ctx) => fromInt32(
ctx.ReadEnum()),
(ref WriteContext output, T value) => output.WriteEnum(toInt32(value)),
value => CodedOutputStream.ComputeEnumSize(toInt32(value)), tag, defaultValue);
}
/// <summary>
/// Retrieves a codec suitable for a message field with the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="parser">A parser to use for the message type.</param>
/// <returns>A codec for the given tag.</returns>
public static FieldCodec<T> ForMessage<T>(uint tag, MessageParser<T> parser) where T : class, IMessage<T>
{
return new FieldCodec<T>(
(ref ParseContext ctx) =>
{
T message = parser.CreateTemplate();
ctx.ReadMessage(message);
return message;
},
(ref WriteContext output, T value) => output.WriteMessage(value),
(ref ParseContext ctx, ref T v) =>
{
if (v == null)
{
v = parser.CreateTemplate();
}
ctx.ReadMessage(v);
},
(ref T v, T v2) =>
{
if (v2 == null)
{
return false;
}
else if (v == null)
{
v = v2.Clone();
}
else
{
v.MergeFrom(v2);
}
return true;
},
message => CodedOutputStream.ComputeMessageSize(message), tag);
}
/// <summary>
/// Retrieves a codec suitable for a group field with the given tag.
/// </summary>
/// <param name="startTag">The start group tag.</param>
/// <param name="endTag">The end group tag.</param>
/// <param name="parser">A parser to use for the group message type.</param>
/// <returns>A codec for given tag</returns>
public static FieldCodec<T> ForGroup<T>(uint startTag, uint endTag, MessageParser<T> parser) where T : class, IMessage<T>
{
return new FieldCodec<T>(
(ref ParseContext ctx) =>
{
T message = parser.CreateTemplate();
ctx.ReadGroup(message);
return message;
},
(ref WriteContext output, T value) => output.WriteGroup(value),
(ref ParseContext ctx, ref T v) =>
{
if (v == null)
{
v = parser.CreateTemplate();
}
ctx.ReadGroup(v);
},
(ref T v, T v2) =>
{
if (v2 == null)
{
return v == null;
}
else if (v == null)
{
v = v2.Clone();
}
else
{
v.MergeFrom(v2);
}
return true;
},
message => CodedOutputStream.ComputeGroupSize(message), startTag, endTag);
}
/// <summary>
/// Creates a codec for a wrapper type of a class - which must be string or ByteString.
/// </summary>
public static FieldCodec<T> ForClassWrapper<T>(uint tag) where T : class
{
var nestedCodec = WrapperCodecs.GetCodec<T>();
return new FieldCodec<T>(
(ref ParseContext ctx) => WrapperCodecs.Read<T>(ref ctx, nestedCodec),
(ref WriteContext output, T value) => WrapperCodecs.Write<T>(ref output, value, nestedCodec),
(ref ParseContext ctx, ref T v) => v = WrapperCodecs.Read<T>(ref ctx, nestedCodec),
(ref T v, T v2) => { v = v2; return v == null; },
value => WrapperCodecs.CalculateSize<T>(value, nestedCodec),
tag, 0,
null); // Default value for the wrapper
}
/// <summary>
/// Creates a codec for a wrapper type of a struct - which must be Int32, Int64, UInt32, UInt64,
/// Bool, Single or Double.
/// </summary>
public static FieldCodec<T?> ForStructWrapper<T>(uint tag) where T : struct
{
var nestedCodec = WrapperCodecs.GetCodec<T>();
return new FieldCodec<T?>(
WrapperCodecs.GetReader<T>(),
(ref WriteContext output, T? value) => WrapperCodecs.Write<T>(ref output, value.Value, nestedCodec),
(ref ParseContext ctx, ref T? v) => v = WrapperCodecs.Read<T>(ref ctx, nestedCodec),
(ref T? v, T? v2) => { if (v2.HasValue) { v = v2; } return v.HasValue; },
value => value == null ? 0 : WrapperCodecs.CalculateSize<T>(value.Value, nestedCodec),
tag, 0,
null); // Default value for the wrapper
}
/// <summary>
/// Helper code to create codecs for wrapper types.
/// </summary>
/// <remarks>
/// Somewhat ugly with all the static methods, but the conversions involved to/from nullable types make it
/// slightly tricky to improve. So long as we keep the public API (ForClassWrapper, ForStructWrapper) in place,
/// we can refactor later if we come up with something cleaner.
/// </remarks>
private static class WrapperCodecs
{
private static readonly Dictionary<System.Type, object> Codecs = new Dictionary<System.Type, object>
{
{ typeof(bool), ForBool(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) },
{ typeof(int), ForInt32(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) },
{ typeof(long), ForInt64(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) },
{ typeof(uint), ForUInt32(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) },
{ typeof(ulong), ForUInt64(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) },
{ typeof(float), ForFloat(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Fixed32)) },
{ typeof(double), ForDouble(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Fixed64)) },
{ typeof(string), ForString(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.LengthDelimited)) },
{ typeof(ByteString), ForBytes(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.LengthDelimited)) }
};
private static readonly Dictionary<System.Type, object> Readers = new Dictionary<System.Type, object>
{
// TODO: Provide more optimized readers.
{ typeof(bool), (ValueReader<bool?>)ParsingPrimitivesWrappers.ReadBoolWrapper },
{ typeof(int), (ValueReader<int?>)ParsingPrimitivesWrappers.ReadInt32Wrapper },
{ typeof(long), (ValueReader<long?>)ParsingPrimitivesWrappers.ReadInt64Wrapper },
{ typeof(uint), (ValueReader<uint?>)ParsingPrimitivesWrappers.ReadUInt32Wrapper },
{ typeof(ulong), (ValueReader<ulong?>)ParsingPrimitivesWrappers.ReadUInt64Wrapper },
{ typeof(float), BitConverter.IsLittleEndian ?
(ValueReader<float?>)ParsingPrimitivesWrappers.ReadFloatWrapperLittleEndian :
(ValueReader<float?>)ParsingPrimitivesWrappers.ReadFloatWrapperSlow },
{ typeof(double), BitConverter.IsLittleEndian ?
(ValueReader<double?>)ParsingPrimitivesWrappers.ReadDoubleWrapperLittleEndian :
(ValueReader<double?>)ParsingPrimitivesWrappers.ReadDoubleWrapperSlow },
// `string` and `ByteString` less performance-sensitive. Do not implement for now.
{ typeof(string), null },
{ typeof(ByteString), null },
};
/// <summary>
/// Returns a field codec which effectively wraps a value of type T in a message.
///
/// </summary>
internal static FieldCodec<T> GetCodec<T>()
{
object value;
if (!Codecs.TryGetValue(typeof(T), out value))
{
throw new InvalidOperationException("Invalid type argument requested for wrapper codec: " + typeof(T));
}
return (FieldCodec<T>) value;
}
internal static ValueReader<T?> GetReader<T>() where T : struct
{
object value;
if (!Readers.TryGetValue(typeof(T), out value))
{
throw new InvalidOperationException("Invalid type argument requested for wrapper reader: " + typeof(T));
}
if (value == null)
{
// Return default unoptimized reader for the wrapper type.
var nestedCoded = GetCodec<T>();
return (ref ParseContext ctx) => Read<T>(ref ctx, nestedCoded);
}
// Return optimized read for the wrapper type.
return (ValueReader<T?>)value;
}
[SecuritySafeCritical]
internal static T Read<T>(ref ParseContext ctx, FieldCodec<T> codec)
{
int length = ctx.ReadLength();
int oldLimit = SegmentedBufferHelper.PushLimit(ref ctx.state, length);
uint tag;
T value = codec.DefaultValue;
while ((tag = ctx.ReadTag()) != 0)
{
if (tag == codec.Tag)
{
value = codec.Read(ref ctx);
}
else
{
ParsingPrimitivesMessages.SkipLastField(ref ctx.buffer, ref ctx.state);
}
}
ParsingPrimitivesMessages.CheckReadEndOfStreamTag(ref ctx.state);
SegmentedBufferHelper.PopLimit(ref ctx.state, oldLimit);
return value;
}
internal static void Write<T>(ref WriteContext ctx, T value, FieldCodec<T> codec)
{
ctx.WriteLength(codec.CalculateSizeWithTag(value));
codec.WriteTagAndValue(ref ctx, value);
}
internal static int CalculateSize<T>(T value, FieldCodec<T> codec)
{
int fieldLength = codec.CalculateSizeWithTag(value);
return CodedOutputStream.ComputeLengthSize(fieldLength) + fieldLength;
}
}
}
internal delegate TValue ValueReader<out TValue>(ref ParseContext ctx);
internal delegate void ValueWriter<T>(ref WriteContext ctx, T value);
/// <summary>
/// <para>
/// An encode/decode pair for a single field. This effectively encapsulates
/// all the information needed to read or write the field value from/to a coded
/// stream.
/// </para>
/// <para>
/// This class is public and has to be as it is used by generated code, but its public
/// API is very limited - just what the generated code needs to call directly.
/// </para>
/// </summary>
/// <remarks>
/// This never writes default values to the stream, and does not address "packedness"
/// in repeated fields itself, other than to know whether or not the field *should* be packed.
/// </remarks>
public sealed class FieldCodec<T>
{
private static readonly EqualityComparer<T> EqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<T>();
private static readonly T DefaultDefault;
// Only non-nullable value types support packing. This is the simplest way of detecting that.
private static readonly bool TypeSupportsPacking = default(T) != null;
/// <summary>
/// Merges an input stream into a value
/// </summary>
internal delegate void InputMerger(ref ParseContext ctx, ref T value);
/// <summary>
/// Merges a value into a reference to another value, returning a boolean if the value was set
/// </summary>
internal delegate bool ValuesMerger(ref T value, T other);
static FieldCodec()
{
if (typeof(T) == typeof(string))
{
DefaultDefault = (T)(object)"";
}
else if (typeof(T) == typeof(ByteString))
{
DefaultDefault = (T)(object)ByteString.Empty;
}
// Otherwise it's the default value of the CLR type
}
internal static bool IsPackedRepeatedField(uint tag) =>
TypeSupportsPacking && WireFormat.GetTagWireType(tag) == WireFormat.WireType.LengthDelimited;
internal bool PackedRepeatedField { get; }
/// <summary>
/// Returns a delegate to write a value (unconditionally) to a coded output stream.
/// </summary>
internal ValueWriter<T> ValueWriter { get; }
/// <summary>
/// Returns the size calculator for just a value.
/// </summary>
internal Func<T, int> ValueSizeCalculator { get; }
/// <summary>
/// Returns a delegate to read a value from a coded input stream. It is assumed that
/// the stream is already positioned on the appropriate tag.
/// </summary>
internal ValueReader<T> ValueReader { get; }
/// <summary>
/// Returns a delegate to merge a value from a coded input stream.
/// It is assumed that the stream is already positioned on the appropriate tag
/// </summary>
internal InputMerger ValueMerger { get; }
/// <summary>
/// Returns a delegate to merge two values together.
/// </summary>
internal ValuesMerger FieldMerger { get; }
/// <summary>
/// Returns the fixed size for an entry, or 0 if sizes vary.
/// </summary>
internal int FixedSize { get; }
/// <summary>
/// Gets the tag of the codec.
/// </summary>
/// <value>
/// The tag of the codec.
/// </value>
internal uint Tag { get; }
/// <summary>
/// Gets the end tag of the codec or 0 if there is no end tag
/// </summary>
/// <value>
/// The end tag of the codec.
/// </value>
internal uint EndTag { get; }
/// <summary>
/// Default value for this codec. Usually the same for every instance of the same type, but
/// for string/ByteString wrapper fields the codec's default value is null, whereas for
/// other string/ByteString fields it's "" or ByteString.Empty.
/// </summary>
/// <value>
/// The default value of the codec's type.
/// </value>
internal T DefaultValue { get; }
private readonly int tagSize;
internal FieldCodec(
ValueReader<T> reader,
ValueWriter<T> writer,
int fixedSize,
uint tag,
T defaultValue) : this(reader, writer, _ => fixedSize, tag, defaultValue)
{
FixedSize = fixedSize;
}
internal FieldCodec(
ValueReader<T> reader,
ValueWriter<T> writer,
Func<T, int> sizeCalculator,
uint tag,
T defaultValue) : this(reader, writer, (ref ParseContext ctx, ref T v) => v = reader(ref ctx), (ref T v, T v2) => { v = v2; return true; }, sizeCalculator, tag, 0, defaultValue)
{
}
internal FieldCodec(
ValueReader<T> reader,
ValueWriter<T> writer,
InputMerger inputMerger,
ValuesMerger valuesMerger,
Func<T, int> sizeCalculator,
uint tag,
uint endTag = 0) : this(reader, writer, inputMerger, valuesMerger, sizeCalculator, tag, endTag, DefaultDefault)
{
}
internal FieldCodec(
ValueReader<T> reader,
ValueWriter<T> writer,
InputMerger inputMerger,
ValuesMerger valuesMerger,
Func<T, int> sizeCalculator,
uint tag,
uint endTag,
T defaultValue)
{
ValueReader = reader;
ValueWriter = writer;
ValueMerger = inputMerger;
FieldMerger = valuesMerger;
ValueSizeCalculator = sizeCalculator;
FixedSize = 0;
Tag = tag;
EndTag = endTag;
DefaultValue = defaultValue;
tagSize = CodedOutputStream.ComputeRawVarint32Size(tag);
if (endTag != 0)
tagSize += CodedOutputStream.ComputeRawVarint32Size(endTag);
// Detect packed-ness once, so we can check for it within RepeatedField<T>.
PackedRepeatedField = IsPackedRepeatedField(tag);
}
/// <summary>
/// Write a tag and the given value, *if* the value is not the default.
/// </summary>
public void WriteTagAndValue(CodedOutputStream output, T value)
{
WriteContext.Initialize(output, out WriteContext ctx);
try
{
WriteTagAndValue(ref ctx, value);
}
finally
{
ctx.CopyStateTo(output);
}
//if (!IsDefault(value))
//{
// output.WriteTag(Tag);
// ValueWriter(output, value);
// if (EndTag != 0)
// {
// output.WriteTag(EndTag);
// }
//}
}
/// <summary>
/// Write a tag and the given value, *if* the value is not the default.
/// </summary>
public void WriteTagAndValue(ref WriteContext ctx, T value)
{
if (!IsDefault(value))
{
ctx.WriteTag(Tag);
ValueWriter(ref ctx, value);
if (EndTag != 0)
{
ctx.WriteTag(EndTag);
}
}
}
/// <summary>
/// Reads a value of the codec type from the given <see cref="CodedInputStream"/>.
/// </summary>
/// <param name="input">The input stream to read from.</param>
/// <returns>The value read from the stream.</returns>
public T Read(CodedInputStream input)
{
ParseContext.Initialize(input, out ParseContext ctx);
try
{
return ValueReader(ref ctx);
}
finally
{
ctx.CopyStateTo(input);
}
}
/// <summary>
/// Reads a value of the codec type from the given <see cref="ParseContext"/>.
/// </summary>
/// <param name="ctx">The parse context to read from.</param>
/// <returns>The value read.</returns>
public T Read(ref ParseContext ctx)
{
return ValueReader(ref ctx);
}
/// <summary>
/// Calculates the size required to write the given value, with a tag,
/// if the value is not the default.
/// </summary>
public int CalculateSizeWithTag(T value) => IsDefault(value) ? 0 : ValueSizeCalculator(value) + tagSize;
private bool IsDefault(T value) => EqualityComparer.Equals(value, DefaultValue);
}
}

View File

@ -0,0 +1,365 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using LC.Google.Protobuf.Reflection;
using LC.Google.Protobuf.WellKnownTypes;
namespace LC.Google.Protobuf
{
/// <summary>
/// <para>A tree representation of a FieldMask. Each leaf node in this tree represent
/// a field path in the FieldMask.</para>
///
/// <para>For example, FieldMask "foo.bar,foo.baz,bar.baz" as a tree will be:</para>
/// <code>
/// [root] -+- foo -+- bar
/// | |
/// | +- baz
/// |
/// +- bar --- baz
/// </code>
///
/// <para>By representing FieldMasks with this tree structure we can easily convert
/// a FieldMask to a canonical form, merge two FieldMasks, calculate the
/// intersection to two FieldMasks and traverse all fields specified by the
/// FieldMask in a message tree.</para>
/// </summary>
internal sealed class FieldMaskTree
{
private const char FIELD_PATH_SEPARATOR = '.';
internal sealed class Node
{
public Dictionary<string, Node> Children { get; } = new Dictionary<string, Node>();
}
private readonly Node root = new Node();
/// <summary>
/// Creates an empty FieldMaskTree.
/// </summary>
public FieldMaskTree()
{
}
/// <summary>
/// Creates a FieldMaskTree for a given FieldMask.
/// </summary>
public FieldMaskTree(FieldMask mask)
{
MergeFromFieldMask(mask);
}
public override string ToString()
{
return ToFieldMask().ToString();
}
/// <summary>
/// Adds a field path to the tree. In a FieldMask, every field path matches the
/// specified field as well as all its sub-fields. For example, a field path
/// "foo.bar" matches field "foo.bar" and also "foo.bar.baz", etc. When adding
/// a field path to the tree, redundant sub-paths will be removed. That is,
/// after adding "foo.bar" to the tree, "foo.bar.baz" will be removed if it
/// exists, which will turn the tree node for "foo.bar" to a leaf node.
/// Likewise, if the field path to add is a sub-path of an existing leaf node,
/// nothing will be changed in the tree.
/// </summary>
public FieldMaskTree AddFieldPath(string path)
{
var parts = path.Split(FIELD_PATH_SEPARATOR);
if (parts.Length == 0)
{
return this;
}
var node = root;
var createNewBranch = false;
// Find the matching node in the tree.
foreach (var part in parts)
{
// Check whether the path matches an existing leaf node.
if (!createNewBranch
&& node != root
&& node.Children.Count == 0)
{
// The path to add is a sub-path of an existing leaf node.
return this;
}
Node childNode;
if (!node.Children.TryGetValue(part, out childNode))
{
createNewBranch = true;
childNode = new Node();
node.Children.Add(part, childNode);
}
node = childNode;
}
// Turn the matching node into a leaf node (i.e., remove sub-paths).
node.Children.Clear();
return this;
}
/// <summary>
/// Merges all field paths in a FieldMask into this tree.
/// </summary>
public FieldMaskTree MergeFromFieldMask(FieldMask mask)
{
foreach (var path in mask.Paths)
{
AddFieldPath(path);
}
return this;
}
/// <summary>
/// Converts this tree to a FieldMask.
/// </summary>
public FieldMask ToFieldMask()
{
var mask = new FieldMask();
if (root.Children.Count != 0)
{
var paths = new List<string>();
GetFieldPaths(root, "", paths);
mask.Paths.AddRange(paths);
}
return mask;
}
/// <summary>
/// Gathers all field paths in a sub-tree.
/// </summary>
private void GetFieldPaths(Node node, string path, List<string> paths)
{
if (node.Children.Count == 0)
{
paths.Add(path);
return;
}
foreach (var entry in node.Children)
{
var childPath = path.Length == 0 ? entry.Key : path + "." + entry.Key;
GetFieldPaths(entry.Value, childPath, paths);
}
}
/// <summary>
/// Adds the intersection of this tree with the given <paramref name="path"/> to <paramref name="output"/>.
/// </summary>
public void IntersectFieldPath(string path, FieldMaskTree output)
{
if (root.Children.Count == 0)
{
return;
}
var parts = path.Split(FIELD_PATH_SEPARATOR);
if (parts.Length == 0)
{
return;
}
var node = root;
foreach (var part in parts)
{
if (node != root
&& node.Children.Count == 0)
{
// The given path is a sub-path of an existing leaf node in the tree.
output.AddFieldPath(path);
return;
}
if (!node.Children.TryGetValue(part, out node))
{
return;
}
}
// We found a matching node for the path. All leaf children of this matching
// node is in the intersection.
var paths = new List<string>();
GetFieldPaths(node, path, paths);
foreach (var value in paths)
{
output.AddFieldPath(value);
}
}
/// <summary>
/// Merges all fields specified by this FieldMaskTree from <paramref name="source"/> to <paramref name="destination"/>.
/// </summary>
public void Merge(IMessage source, IMessage destination, FieldMask.MergeOptions options)
{
if (source.Descriptor != destination.Descriptor)
{
throw new InvalidProtocolBufferException("Cannot merge messages of different types.");
}
if (root.Children.Count == 0)
{
return;
}
Merge(root, "", source, destination, options);
}
/// <summary>
/// Merges all fields specified by a sub-tree from <paramref name="source"/> to <paramref name="destination"/>.
/// </summary>
private void Merge(
Node node,
string path,
IMessage source,
IMessage destination,
FieldMask.MergeOptions options)
{
if (source.Descriptor != destination.Descriptor)
{
throw new InvalidProtocolBufferException($"source ({source.Descriptor}) and destination ({destination.Descriptor}) descriptor must be equal");
}
var descriptor = source.Descriptor;
foreach (var entry in node.Children)
{
var field = descriptor.FindFieldByName(entry.Key);
if (field == null)
{
Debug.WriteLine($"Cannot find field \"{entry.Key}\" in message type \"{descriptor.FullName}\"");
continue;
}
if (entry.Value.Children.Count != 0)
{
if (field.IsRepeated
|| field.FieldType != FieldType.Message)
{
Debug.WriteLine($"Field \"{field.FullName}\" is not a singular message field and cannot have sub-fields.");
continue;
}
var sourceField = field.Accessor.GetValue(source);
var destinationField = field.Accessor.GetValue(destination);
if (sourceField == null
&& destinationField == null)
{
// If the message field is not present in both source and destination, skip recursing
// so we don't create unnecessary empty messages.
continue;
}
if (destinationField == null)
{
// If we have to merge but the destination does not contain the field, create it.
destinationField = field.MessageType.Parser.CreateTemplate();
field.Accessor.SetValue(destination, destinationField);
}
var childPath = path.Length == 0 ? entry.Key : path + "." + entry.Key;
Merge(entry.Value, childPath, (IMessage)sourceField, (IMessage)destinationField, options);
continue;
}
if (field.IsRepeated)
{
if (options.ReplaceRepeatedFields)
{
field.Accessor.Clear(destination);
}
var sourceField = (IList)field.Accessor.GetValue(source);
var destinationField = (IList)field.Accessor.GetValue(destination);
foreach (var element in sourceField)
{
destinationField.Add(element);
}
}
else
{
var sourceField = field.Accessor.GetValue(source);
if (field.FieldType == FieldType.Message)
{
if (options.ReplaceMessageFields)
{
if (sourceField == null)
{
field.Accessor.Clear(destination);
}
else
{
field.Accessor.SetValue(destination, sourceField);
}
}
else
{
if (sourceField != null)
{
var sourceByteString = ((IMessage)sourceField).ToByteString();
var destinationValue = (IMessage)field.Accessor.GetValue(destination);
if (destinationValue != null)
{
destinationValue.MergeFrom(sourceByteString);
}
else
{
field.Accessor.SetValue(destination, field.MessageType.Parser.ParseFrom(sourceByteString));
}
}
}
}
else
{
if (sourceField != null
|| !options.ReplacePrimitiveFields)
{
field.Accessor.SetValue(destination, sourceField);
}
else
{
field.Accessor.Clear(destination);
}
}
}
}
}
}
}

View File

@ -0,0 +1,49 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Text.RegularExpressions;
namespace LC.Google.Protobuf
{
/// <summary>
/// Class containing helpful workarounds for various platform compatibility
/// </summary>
internal static class FrameworkPortability
{
// The value of RegexOptions.Compiled is 8. We can test for the presence at
// execution time using Enum.IsDefined, so a single build will do the right thing
// on each platform. (RegexOptions.Compiled isn't supported by PCLs.)
internal static readonly RegexOptions CompiledRegexWhereAvailable =
Enum.IsDefined(typeof(RegexOptions), 8) ? (RegexOptions)8 : RegexOptions.None;
}
}

View File

@ -0,0 +1,55 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
namespace LC.Google.Protobuf
{
#if GOOGLE_PROTOBUF_SUPPORT_SYSTEM_MEMORY
/// <summary>
/// Interface for a Protocol Buffers message, supporting
/// parsing from <see cref="ParseContext"/> and writing to <see cref="WriteContext"/>.
/// </summary>
public interface IBufferMessage : IMessage
{
/// <summary>
/// Internal implementation of merging data from given parse context into this message.
/// Users should never invoke this method directly.
/// </summary>
void InternalMergeFrom(ref ParseContext ctx);
/// <summary>
/// Internal implementation of writing this message to a given write context.
/// Users should never invoke this method directly.
/// </summary>
void InternalWriteTo(ref WriteContext ctx);
}
#endif
}

View File

@ -0,0 +1,69 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2016 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
namespace LC.Google.Protobuf
{
/// <summary>
/// A message type that has a custom string format for diagnostic purposes.
/// </summary>
/// <remarks>
/// <para>
/// Calling <see cref="object.ToString"/> on a generated message type normally
/// returns the JSON representation. If a message type implements this interface,
/// then the <see cref="ToDiagnosticString"/> method will be called instead of the regular
/// JSON formatting code, but only when <c>ToString()</c> is called either on the message itself
/// or on another message which contains it. This does not affect the normal JSON formatting of
/// the message.
/// </para>
/// <para>
/// For example, if you create a proto message representing a GUID, the internal
/// representation may be a <c>bytes</c> field or four <c>fixed32</c> fields. However, when debugging
/// it may be more convenient to see a result in the same format as <see cref="System.Guid"/> provides.
/// </para>
/// <para>This interface extends <see cref="IMessage"/> to avoid it accidentally being implemented
/// on types other than messages, where it would not be used by anything in the framework.</para>
/// </remarks>
public interface ICustomDiagnosticMessage : IMessage
{
/// <summary>
/// Returns a string representation of this object, for diagnostic purposes.
/// </summary>
/// <remarks>
/// This method is called when a message is formatted as part of a <see cref="object.ToString"/>
/// call. It does not affect the JSON representation used by <see cref="JsonFormatter"/> other than
/// in calls to <see cref="JsonFormatter.ToDiagnosticString(IMessage)"/>. While it is recommended
/// that the result is valid JSON, this is never assumed by the Protobuf library.
/// </remarks>
/// <returns>A string representation of this object, for diagnostic purposes.</returns>
string ToDiagnosticString();
}
}

View File

@ -0,0 +1,54 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
namespace LC.Google.Protobuf
{
/// <summary>
/// Generic interface for a deeply cloneable type.
/// </summary>
/// <remarks>
/// <para>
/// All generated messages implement this interface, but so do some non-message types.
/// Additionally, due to the type constraint on <c>T</c> in <see cref="IMessage{T}"/>,
/// it is simpler to keep this as a separate interface.
/// </para>
/// </remarks>
/// <typeparam name="T">The type itself, returned by the <see cref="Clone"/> method.</typeparam>
public interface IDeepCloneable<T>
{
/// <summary>
/// Creates a deep clone of this object.
/// </summary>
/// <returns>A deep clone of this object.</returns>
T Clone();
}
}

View File

@ -0,0 +1,79 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using LC.Google.Protobuf.Collections;
namespace LC.Google.Protobuf
{
/// <summary>
/// Generic interface for a Protocol Buffers message containing one or more extensions, where the type parameter is expected to be the same type as the implementation class.
/// This interface is experiemental and is subject to change.
/// </summary>
public interface IExtendableMessage<T> : IMessage<T> where T : IExtendableMessage<T>
{
/// <summary>
/// Gets the value of the specified extension
/// </summary>
TValue GetExtension<TValue>(Extension<T, TValue> extension);
/// <summary>
/// Gets the value of the specified repeated extension or null if the extension isn't registered in this set.
/// For a version of this method that never returns null, use <see cref="IExtendableMessage{T}.GetOrInitializeExtension{TValue}(RepeatedExtension{T, TValue})"/>
/// </summary>
RepeatedField<TValue> GetExtension<TValue>(RepeatedExtension<T, TValue> extension);
/// <summary>
/// Gets the value of the specified repeated extension, registering it if it hasn't already been registered.
/// </summary>
RepeatedField<TValue> GetOrInitializeExtension<TValue>(RepeatedExtension<T, TValue> extension);
/// <summary>
/// Sets the value of the specified extension
/// </summary>
void SetExtension<TValue>(Extension<T, TValue> extension, TValue value);
/// <summary>
/// Gets whether the value of the specified extension is set
/// </summary>
bool HasExtension<TValue>(Extension<T, TValue> extension);
/// <summary>
/// Clears the value of the specified extension
/// </summary>
void ClearExtension<TValue>(Extension<T, TValue> extension);
/// <summary>
/// Clears the value of the specified repeated extension
/// </summary>
void ClearExtension<TValue>(RepeatedExtension<T, TValue> extension);
}
}

View File

@ -0,0 +1,87 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using LC.Google.Protobuf.Reflection;
namespace LC.Google.Protobuf
{
/// <summary>
/// Interface for a Protocol Buffers message, supporting
/// basic operations required for serialization.
/// </summary>
public interface IMessage
{
/// <summary>
/// Merges the data from the specified coded input stream with the current message.
/// </summary>
/// <remarks>See the user guide for precise merge semantics.</remarks>
/// <param name="input"></param>
void MergeFrom(CodedInputStream input);
/// <summary>
/// Writes the data to the given coded output stream.
/// </summary>
/// <param name="output">Coded output stream to write the data to. Must not be null.</param>
void WriteTo(CodedOutputStream output);
/// <summary>
/// Calculates the size of this message in Protocol Buffer wire format, in bytes.
/// </summary>
/// <returns>The number of bytes required to write this message
/// to a coded output stream.</returns>
int CalculateSize();
/// <summary>
/// Descriptor for this message. All instances are expected to return the same descriptor,
/// and for generated types this will be an explicitly-implemented member, returning the
/// same value as the static property declared on the type.
/// </summary>
MessageDescriptor Descriptor { get; }
}
/// <summary>
/// Generic interface for a Protocol Buffers message,
/// where the type parameter is expected to be the same type as
/// the implementation class.
/// </summary>
/// <typeparam name="T">The message type.</typeparam>
public interface IMessage<T> : IMessage, IEquatable<T>, IDeepCloneable<T> where T : IMessage<T>
{
/// <summary>
/// Merges the given message into this one.
/// </summary>
/// <remarks>See the user guide for precise merge semantics.</remarks>
/// <param name="message">The message to merge with this one. Must not be null.</param>
void MergeFrom(T message);
}
}

View File

@ -0,0 +1,53 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System.IO;
namespace LC.Google.Protobuf
{
/// <summary>
/// Thrown when an attempt is made to parse invalid JSON, e.g. using
/// a non-string property key, or including a redundant comma. Parsing a protocol buffer
/// message represented in JSON using <see cref="JsonParser"/> can throw both this
/// exception and <see cref="InvalidProtocolBufferException"/> depending on the situation. This
/// exception is only thrown for "pure JSON" errors, whereas <c>InvalidProtocolBufferException</c>
/// is thrown when the JSON may be valid in and of itself, but cannot be parsed as a protocol buffer
/// message.
/// </summary>
public sealed class InvalidJsonException : IOException
{
internal InvalidJsonException(string message)
: base(message)
{
}
}
}

View File

@ -0,0 +1,140 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.IO;
namespace LC.Google.Protobuf
{
/// <summary>
/// Thrown when a protocol message being parsed is invalid in some way,
/// e.g. it contains a malformed varint or a negative byte length.
/// </summary>
public sealed class InvalidProtocolBufferException : IOException
{
internal InvalidProtocolBufferException(string message)
: base(message)
{
}
internal InvalidProtocolBufferException(string message, Exception innerException)
: base(message, innerException)
{
}
internal static InvalidProtocolBufferException MoreDataAvailable()
{
return new InvalidProtocolBufferException(
"Completed reading a message while more data was available in the stream.");
}
internal static InvalidProtocolBufferException TruncatedMessage()
{
return new InvalidProtocolBufferException(
"While parsing a protocol message, the input ended unexpectedly " +
"in the middle of a field. This could mean either that the " +
"input has been truncated or that an embedded message " +
"misreported its own length.");
}
internal static InvalidProtocolBufferException NegativeSize()
{
return new InvalidProtocolBufferException(
"CodedInputStream encountered an embedded string or message " +
"which claimed to have negative size.");
}
internal static InvalidProtocolBufferException MalformedVarint()
{
return new InvalidProtocolBufferException(
"CodedInputStream encountered a malformed varint.");
}
/// <summary>
/// Creates an exception for an error condition of an invalid tag being encountered.
/// </summary>
internal static InvalidProtocolBufferException InvalidTag()
{
return new InvalidProtocolBufferException(
"Protocol message contained an invalid tag (zero).");
}
internal static InvalidProtocolBufferException InvalidWireType()
{
return new InvalidProtocolBufferException(
"Protocol message contained a tag with an invalid wire type.");
}
internal static InvalidProtocolBufferException InvalidBase64(Exception innerException)
{
return new InvalidProtocolBufferException("Invalid base64 data", innerException);
}
internal static InvalidProtocolBufferException InvalidEndTag()
{
return new InvalidProtocolBufferException(
"Protocol message end-group tag did not match expected tag.");
}
internal static InvalidProtocolBufferException RecursionLimitExceeded()
{
return new InvalidProtocolBufferException(
"Protocol message had too many levels of nesting. May be malicious. " +
"Use CodedInputStream.SetRecursionLimit() to increase the depth limit.");
}
internal static InvalidProtocolBufferException JsonRecursionLimitExceeded()
{
return new InvalidProtocolBufferException(
"Protocol message had too many levels of nesting. May be malicious. " +
"Use JsonParser.Settings to increase the depth limit.");
}
internal static InvalidProtocolBufferException SizeLimitExceeded()
{
return new InvalidProtocolBufferException(
"Protocol message was too large. May be malicious. " +
"Use CodedInputStream.SetSizeLimit() to increase the size limit.");
}
internal static InvalidProtocolBufferException InvalidMessageStreamTag()
{
return new InvalidProtocolBufferException(
"Stream of protocol messages had invalid tag. Expected tag is length-delimited field 1.");
}
internal static InvalidProtocolBufferException MissingFields()
{
return new InvalidProtocolBufferException("Message was missing required fields");
}
}
}

View File

@ -0,0 +1,927 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Collections;
using System.Globalization;
using System.Text;
using LC.Google.Protobuf.Reflection;
using LC.Google.Protobuf.WellKnownTypes;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
namespace LC.Google.Protobuf
{
/// <summary>
/// Reflection-based converter from messages to JSON.
/// </summary>
/// <remarks>
/// <para>
/// Instances of this class are thread-safe, with no mutable state.
/// </para>
/// <para>
/// This is a simple start to get JSON formatting working. As it's reflection-based,
/// it's not as quick as baking calls into generated messages - but is a simpler implementation.
/// (This code is generally not heavily optimized.)
/// </para>
/// </remarks>
public sealed class JsonFormatter
{
internal const string AnyTypeUrlField = "@type";
internal const string AnyDiagnosticValueField = "@value";
internal const string AnyWellKnownTypeValueField = "value";
private const string TypeUrlPrefix = "type.googleapis.com";
private const string NameValueSeparator = ": ";
private const string PropertySeparator = ", ";
/// <summary>
/// Returns a formatter using the default settings.
/// </summary>
public static JsonFormatter Default { get; } = new JsonFormatter(Settings.Default);
// A JSON formatter which *only* exists
private static readonly JsonFormatter diagnosticFormatter = new JsonFormatter(Settings.Default);
/// <summary>
/// The JSON representation of the first 160 characters of Unicode.
/// Empty strings are replaced by the static constructor.
/// </summary>
private static readonly string[] CommonRepresentations = {
// C0 (ASCII and derivatives) control characters
"\\u0000", "\\u0001", "\\u0002", "\\u0003", // 0x00
"\\u0004", "\\u0005", "\\u0006", "\\u0007",
"\\b", "\\t", "\\n", "\\u000b",
"\\f", "\\r", "\\u000e", "\\u000f",
"\\u0010", "\\u0011", "\\u0012", "\\u0013", // 0x10
"\\u0014", "\\u0015", "\\u0016", "\\u0017",
"\\u0018", "\\u0019", "\\u001a", "\\u001b",
"\\u001c", "\\u001d", "\\u001e", "\\u001f",
// Escaping of " and \ are required by www.json.org string definition.
// Escaping of < and > are required for HTML security.
"", "", "\\\"", "", "", "", "", "", // 0x20
"", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", // 0x30
"", "", "", "", "\\u003c", "", "\\u003e", "",
"", "", "", "", "", "", "", "", // 0x40
"", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", // 0x50
"", "", "", "", "\\\\", "", "", "",
"", "", "", "", "", "", "", "", // 0x60
"", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", // 0x70
"", "", "", "", "", "", "", "\\u007f",
// C1 (ISO 8859 and Unicode) extended control characters
"\\u0080", "\\u0081", "\\u0082", "\\u0083", // 0x80
"\\u0084", "\\u0085", "\\u0086", "\\u0087",
"\\u0088", "\\u0089", "\\u008a", "\\u008b",
"\\u008c", "\\u008d", "\\u008e", "\\u008f",
"\\u0090", "\\u0091", "\\u0092", "\\u0093", // 0x90
"\\u0094", "\\u0095", "\\u0096", "\\u0097",
"\\u0098", "\\u0099", "\\u009a", "\\u009b",
"\\u009c", "\\u009d", "\\u009e", "\\u009f"
};
static JsonFormatter()
{
for (int i = 0; i < CommonRepresentations.Length; i++)
{
if (CommonRepresentations[i] == "")
{
CommonRepresentations[i] = ((char) i).ToString();
}
}
}
private readonly Settings settings;
private bool DiagnosticOnly => ReferenceEquals(this, diagnosticFormatter);
/// <summary>
/// Creates a new formatted with the given settings.
/// </summary>
/// <param name="settings">The settings.</param>
public JsonFormatter(Settings settings)
{
this.settings = ProtoPreconditions.CheckNotNull(settings, nameof(settings));
}
/// <summary>
/// Formats the specified message as JSON.
/// </summary>
/// <param name="message">The message to format.</param>
/// <returns>The formatted message.</returns>
public string Format(IMessage message)
{
var writer = new StringWriter();
Format(message, writer);
return writer.ToString();
}
/// <summary>
/// Formats the specified message as JSON.
/// </summary>
/// <param name="message">The message to format.</param>
/// <param name="writer">The TextWriter to write the formatted message to.</param>
/// <returns>The formatted message.</returns>
public void Format(IMessage message, TextWriter writer)
{
ProtoPreconditions.CheckNotNull(message, nameof(message));
ProtoPreconditions.CheckNotNull(writer, nameof(writer));
if (message.Descriptor.IsWellKnownType)
{
WriteWellKnownTypeValue(writer, message.Descriptor, message);
}
else
{
WriteMessage(writer, message);
}
}
/// <summary>
/// Converts a message to JSON for diagnostic purposes with no extra context.
/// </summary>
/// <remarks>
/// <para>
/// This differs from calling <see cref="Format(IMessage)"/> on the default JSON
/// formatter in its handling of <see cref="Any"/>. As no type registry is available
/// in <see cref="object.ToString"/> calls, the normal way of resolving the type of
/// an <c>Any</c> message cannot be applied. Instead, a JSON property named <c>@value</c>
/// is included with the base64 data from the <see cref="Any.Value"/> property of the message.
/// </para>
/// <para>The value returned by this method is only designed to be used for diagnostic
/// purposes. It may not be parsable by <see cref="JsonParser"/>, and may not be parsable
/// by other Protocol Buffer implementations.</para>
/// </remarks>
/// <param name="message">The message to format for diagnostic purposes.</param>
/// <returns>The diagnostic-only JSON representation of the message</returns>
public static string ToDiagnosticString(IMessage message)
{
ProtoPreconditions.CheckNotNull(message, nameof(message));
return diagnosticFormatter.Format(message);
}
private void WriteMessage(TextWriter writer, IMessage message)
{
if (message == null)
{
WriteNull(writer);
return;
}
if (DiagnosticOnly)
{
ICustomDiagnosticMessage customDiagnosticMessage = message as ICustomDiagnosticMessage;
if (customDiagnosticMessage != null)
{
writer.Write(customDiagnosticMessage.ToDiagnosticString());
return;
}
}
writer.Write("{ ");
bool writtenFields = WriteMessageFields(writer, message, false);
writer.Write(writtenFields ? " }" : "}");
}
private bool WriteMessageFields(TextWriter writer, IMessage message, bool assumeFirstFieldWritten)
{
var fields = message.Descriptor.Fields;
bool first = !assumeFirstFieldWritten;
// First non-oneof fields
foreach (var field in fields.InFieldNumberOrder())
{
var accessor = field.Accessor;
var value = accessor.GetValue(message);
if (!ShouldFormatFieldValue(message, field, value))
{
continue;
}
if (!first)
{
writer.Write(PropertySeparator);
}
WriteString(writer, accessor.Descriptor.JsonName);
writer.Write(NameValueSeparator);
WriteValue(writer, value);
first = false;
}
return !first;
}
/// <summary>
/// Determines whether or not a field value should be serialized according to the field,
/// its value in the message, and the settings of this formatter.
/// </summary>
private bool ShouldFormatFieldValue(IMessage message, FieldDescriptor field, object value) =>
field.HasPresence
// Fields that support presence *just* use that
? field.Accessor.HasValue(message)
// Otherwise, format if either we've been asked to format default values, or if it's
// not a default value anyway.
: settings.FormatDefaultValues || !IsDefaultValue(field, value);
// Converted from java/core/src/main/java/com/google/protobuf/Descriptors.java
internal static string ToJsonName(string name)
{
StringBuilder result = new StringBuilder(name.Length);
bool isNextUpperCase = false;
foreach (char ch in name)
{
if (ch == '_')
{
isNextUpperCase = true;
}
else if (isNextUpperCase)
{
result.Append(char.ToUpperInvariant(ch));
isNextUpperCase = false;
}
else
{
result.Append(ch);
}
}
return result.ToString();
}
internal static string FromJsonName(string name)
{
StringBuilder result = new StringBuilder(name.Length);
foreach (char ch in name)
{
if (char.IsUpper(ch))
{
result.Append('_');
result.Append(char.ToLowerInvariant(ch));
}
else
{
result.Append(ch);
}
}
return result.ToString();
}
private static void WriteNull(TextWriter writer)
{
writer.Write("null");
}
private static bool IsDefaultValue(FieldDescriptor descriptor, object value)
{
if (descriptor.IsMap)
{
IDictionary dictionary = (IDictionary) value;
return dictionary.Count == 0;
}
if (descriptor.IsRepeated)
{
IList list = (IList) value;
return list.Count == 0;
}
switch (descriptor.FieldType)
{
case FieldType.Bool:
return (bool) value == false;
case FieldType.Bytes:
return (ByteString) value == ByteString.Empty;
case FieldType.String:
return (string) value == "";
case FieldType.Double:
return (double) value == 0.0;
case FieldType.SInt32:
case FieldType.Int32:
case FieldType.SFixed32:
case FieldType.Enum:
return (int) value == 0;
case FieldType.Fixed32:
case FieldType.UInt32:
return (uint) value == 0;
case FieldType.Fixed64:
case FieldType.UInt64:
return (ulong) value == 0;
case FieldType.SFixed64:
case FieldType.Int64:
case FieldType.SInt64:
return (long) value == 0;
case FieldType.Float:
return (float) value == 0f;
case FieldType.Message:
case FieldType.Group: // Never expect to get this, but...
return value == null;
default:
throw new ArgumentException("Invalid field type");
}
}
/// <summary>
/// Writes a single value to the given writer as JSON. Only types understood by
/// Protocol Buffers can be written in this way. This method is only exposed for
/// advanced use cases; most users should be using <see cref="Format(IMessage)"/>
/// or <see cref="Format(IMessage, TextWriter)"/>.
/// </summary>
/// <param name="writer">The writer to write the value to. Must not be null.</param>
/// <param name="value">The value to write. May be null.</param>
public void WriteValue(TextWriter writer, object value)
{
if (value == null || value is NullValue)
{
WriteNull(writer);
}
else if (value is bool)
{
writer.Write((bool)value ? "true" : "false");
}
else if (value is ByteString)
{
// Nothing in Base64 needs escaping
writer.Write('"');
writer.Write(((ByteString)value).ToBase64());
writer.Write('"');
}
else if (value is string)
{
WriteString(writer, (string)value);
}
else if (value is IDictionary)
{
WriteDictionary(writer, (IDictionary)value);
}
else if (value is IList)
{
WriteList(writer, (IList)value);
}
else if (value is int || value is uint)
{
IFormattable formattable = (IFormattable) value;
writer.Write(formattable.ToString("d", CultureInfo.InvariantCulture));
}
else if (value is long || value is ulong)
{
writer.Write('"');
IFormattable formattable = (IFormattable) value;
writer.Write(formattable.ToString("d", CultureInfo.InvariantCulture));
writer.Write('"');
}
else if (value is System.Enum)
{
if (settings.FormatEnumsAsIntegers)
{
WriteValue(writer, (int)value);
}
else
{
string name = OriginalEnumValueHelper.GetOriginalName(value);
if (name != null)
{
WriteString(writer, name);
}
else
{
WriteValue(writer, (int)value);
}
}
}
else if (value is float || value is double)
{
string text = ((IFormattable) value).ToString("r", CultureInfo.InvariantCulture);
if (text == "NaN" || text == "Infinity" || text == "-Infinity")
{
writer.Write('"');
writer.Write(text);
writer.Write('"');
}
else
{
writer.Write(text);
}
}
else if (value is IMessage)
{
Format((IMessage)value, writer);
}
else
{
throw new ArgumentException("Unable to format value of type " + value.GetType());
}
}
/// <summary>
/// Central interception point for well-known type formatting. Any well-known types which
/// don't need special handling can fall back to WriteMessage. We avoid assuming that the
/// values are using the embedded well-known types, in order to allow for dynamic messages
/// in the future.
/// </summary>
private void WriteWellKnownTypeValue(TextWriter writer, MessageDescriptor descriptor, object value)
{
// Currently, we can never actually get here, because null values are always handled by the caller. But if we *could*,
// this would do the right thing.
if (value == null)
{
WriteNull(writer);
return;
}
// For wrapper types, the value will either be the (possibly boxed) "native" value,
// or the message itself if we're formatting it at the top level (e.g. just calling ToString on the object itself).
// If it's the message form, we can extract the value first, which *will* be the (possibly boxed) native value,
// and then proceed, writing it as if we were definitely in a field. (We never need to wrap it in an extra string...
// WriteValue will do the right thing.)
if (descriptor.IsWrapperType)
{
if (value is IMessage)
{
var message = (IMessage) value;
value = message.Descriptor.Fields[WrappersReflection.WrapperValueFieldNumber].Accessor.GetValue(message);
}
WriteValue(writer, value);
return;
}
if (descriptor.FullName == Timestamp.Descriptor.FullName)
{
WriteTimestamp(writer, (IMessage)value);
return;
}
if (descriptor.FullName == Duration.Descriptor.FullName)
{
WriteDuration(writer, (IMessage)value);
return;
}
if (descriptor.FullName == FieldMask.Descriptor.FullName)
{
WriteFieldMask(writer, (IMessage)value);
return;
}
if (descriptor.FullName == Struct.Descriptor.FullName)
{
WriteStruct(writer, (IMessage)value);
return;
}
if (descriptor.FullName == ListValue.Descriptor.FullName)
{
var fieldAccessor = descriptor.Fields[ListValue.ValuesFieldNumber].Accessor;
WriteList(writer, (IList)fieldAccessor.GetValue((IMessage)value));
return;
}
if (descriptor.FullName == Value.Descriptor.FullName)
{
WriteStructFieldValue(writer, (IMessage)value);
return;
}
if (descriptor.FullName == Any.Descriptor.FullName)
{
WriteAny(writer, (IMessage)value);
return;
}
WriteMessage(writer, (IMessage)value);
}
private void WriteTimestamp(TextWriter writer, IMessage value)
{
// TODO: In the common case where this *is* using the built-in Timestamp type, we could
// avoid all the reflection at this point, by casting to Timestamp. In the interests of
// avoiding subtle bugs, don't do that until we've implemented DynamicMessage so that we can prove
// it still works in that case.
int nanos = (int) value.Descriptor.Fields[Timestamp.NanosFieldNumber].Accessor.GetValue(value);
long seconds = (long) value.Descriptor.Fields[Timestamp.SecondsFieldNumber].Accessor.GetValue(value);
writer.Write(Timestamp.ToJson(seconds, nanos, DiagnosticOnly));
}
private void WriteDuration(TextWriter writer, IMessage value)
{
// TODO: Same as for WriteTimestamp
int nanos = (int) value.Descriptor.Fields[Duration.NanosFieldNumber].Accessor.GetValue(value);
long seconds = (long) value.Descriptor.Fields[Duration.SecondsFieldNumber].Accessor.GetValue(value);
writer.Write(Duration.ToJson(seconds, nanos, DiagnosticOnly));
}
private void WriteFieldMask(TextWriter writer, IMessage value)
{
var paths = (IList<string>) value.Descriptor.Fields[FieldMask.PathsFieldNumber].Accessor.GetValue(value);
writer.Write(FieldMask.ToJson(paths, DiagnosticOnly));
}
private void WriteAny(TextWriter writer, IMessage value)
{
if (DiagnosticOnly)
{
WriteDiagnosticOnlyAny(writer, value);
return;
}
string typeUrl = (string) value.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.GetValue(value);
ByteString data = (ByteString) value.Descriptor.Fields[Any.ValueFieldNumber].Accessor.GetValue(value);
string typeName = Any.GetTypeName(typeUrl);
MessageDescriptor descriptor = settings.TypeRegistry.Find(typeName);
if (descriptor == null)
{
throw new InvalidOperationException($"Type registry has no descriptor for type name '{typeName}'");
}
IMessage message = descriptor.Parser.ParseFrom(data);
writer.Write("{ ");
WriteString(writer, AnyTypeUrlField);
writer.Write(NameValueSeparator);
WriteString(writer, typeUrl);
if (descriptor.IsWellKnownType)
{
writer.Write(PropertySeparator);
WriteString(writer, AnyWellKnownTypeValueField);
writer.Write(NameValueSeparator);
WriteWellKnownTypeValue(writer, descriptor, message);
}
else
{
WriteMessageFields(writer, message, true);
}
writer.Write(" }");
}
private void WriteDiagnosticOnlyAny(TextWriter writer, IMessage value)
{
string typeUrl = (string) value.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.GetValue(value);
ByteString data = (ByteString) value.Descriptor.Fields[Any.ValueFieldNumber].Accessor.GetValue(value);
writer.Write("{ ");
WriteString(writer, AnyTypeUrlField);
writer.Write(NameValueSeparator);
WriteString(writer, typeUrl);
writer.Write(PropertySeparator);
WriteString(writer, AnyDiagnosticValueField);
writer.Write(NameValueSeparator);
writer.Write('"');
writer.Write(data.ToBase64());
writer.Write('"');
writer.Write(" }");
}
private void WriteStruct(TextWriter writer, IMessage message)
{
writer.Write("{ ");
IDictionary fields = (IDictionary) message.Descriptor.Fields[Struct.FieldsFieldNumber].Accessor.GetValue(message);
bool first = true;
foreach (DictionaryEntry entry in fields)
{
string key = (string) entry.Key;
IMessage value = (IMessage) entry.Value;
if (string.IsNullOrEmpty(key) || value == null)
{
throw new InvalidOperationException("Struct fields cannot have an empty key or a null value.");
}
if (!first)
{
writer.Write(PropertySeparator);
}
WriteString(writer, key);
writer.Write(NameValueSeparator);
WriteStructFieldValue(writer, value);
first = false;
}
writer.Write(first ? "}" : " }");
}
private void WriteStructFieldValue(TextWriter writer, IMessage message)
{
var specifiedField = message.Descriptor.Oneofs[0].Accessor.GetCaseFieldDescriptor(message);
if (specifiedField == null)
{
throw new InvalidOperationException("Value message must contain a value for the oneof.");
}
object value = specifiedField.Accessor.GetValue(message);
switch (specifiedField.FieldNumber)
{
case Value.BoolValueFieldNumber:
case Value.StringValueFieldNumber:
case Value.NumberValueFieldNumber:
WriteValue(writer, value);
return;
case Value.StructValueFieldNumber:
case Value.ListValueFieldNumber:
// Structs and ListValues are nested messages, and already well-known types.
var nestedMessage = (IMessage) specifiedField.Accessor.GetValue(message);
WriteWellKnownTypeValue(writer, nestedMessage.Descriptor, nestedMessage);
return;
case Value.NullValueFieldNumber:
WriteNull(writer);
return;
default:
throw new InvalidOperationException("Unexpected case in struct field: " + specifiedField.FieldNumber);
}
}
internal void WriteList(TextWriter writer, IList list)
{
writer.Write("[ ");
bool first = true;
foreach (var value in list)
{
if (!first)
{
writer.Write(PropertySeparator);
}
WriteValue(writer, value);
first = false;
}
writer.Write(first ? "]" : " ]");
}
internal void WriteDictionary(TextWriter writer, IDictionary dictionary)
{
writer.Write("{ ");
bool first = true;
// This will box each pair. Could use IDictionaryEnumerator, but that's ugly in terms of disposal.
foreach (DictionaryEntry pair in dictionary)
{
if (!first)
{
writer.Write(PropertySeparator);
}
string keyText;
if (pair.Key is string)
{
keyText = (string) pair.Key;
}
else if (pair.Key is bool)
{
keyText = (bool) pair.Key ? "true" : "false";
}
else if (pair.Key is int || pair.Key is uint | pair.Key is long || pair.Key is ulong)
{
keyText = ((IFormattable) pair.Key).ToString("d", CultureInfo.InvariantCulture);
}
else
{
if (pair.Key == null)
{
throw new ArgumentException("Dictionary has entry with null key");
}
throw new ArgumentException("Unhandled dictionary key type: " + pair.Key.GetType());
}
WriteString(writer, keyText);
writer.Write(NameValueSeparator);
WriteValue(writer, pair.Value);
first = false;
}
writer.Write(first ? "}" : " }");
}
/// <summary>
/// Writes a string (including leading and trailing double quotes) to a builder, escaping as required.
/// </summary>
/// <remarks>
/// Other than surrogate pair handling, this code is mostly taken from src/google/protobuf/util/internal/json_escaping.cc.
/// </remarks>
internal static void WriteString(TextWriter writer, string text)
{
writer.Write('"');
for (int i = 0; i < text.Length; i++)
{
char c = text[i];
if (c < 0xa0)
{
writer.Write(CommonRepresentations[c]);
continue;
}
if (char.IsHighSurrogate(c))
{
// Encountered first part of a surrogate pair.
// Check that we have the whole pair, and encode both parts as hex.
i++;
if (i == text.Length || !char.IsLowSurrogate(text[i]))
{
throw new ArgumentException("String contains low surrogate not followed by high surrogate");
}
HexEncodeUtf16CodeUnit(writer, c);
HexEncodeUtf16CodeUnit(writer, text[i]);
continue;
}
else if (char.IsLowSurrogate(c))
{
throw new ArgumentException("String contains high surrogate not preceded by low surrogate");
}
switch ((uint) c)
{
// These are not required by json spec
// but used to prevent security bugs in javascript.
case 0xfeff: // Zero width no-break space
case 0xfff9: // Interlinear annotation anchor
case 0xfffa: // Interlinear annotation separator
case 0xfffb: // Interlinear annotation terminator
case 0x00ad: // Soft-hyphen
case 0x06dd: // Arabic end of ayah
case 0x070f: // Syriac abbreviation mark
case 0x17b4: // Khmer vowel inherent Aq
case 0x17b5: // Khmer vowel inherent Aa
HexEncodeUtf16CodeUnit(writer, c);
break;
default:
if ((c >= 0x0600 && c <= 0x0603) || // Arabic signs
(c >= 0x200b && c <= 0x200f) || // Zero width etc.
(c >= 0x2028 && c <= 0x202e) || // Separators etc.
(c >= 0x2060 && c <= 0x2064) || // Invisible etc.
(c >= 0x206a && c <= 0x206f))
{
HexEncodeUtf16CodeUnit(writer, c);
}
else
{
// No handling of surrogates here - that's done earlier
writer.Write(c);
}
break;
}
}
writer.Write('"');
}
private const string Hex = "0123456789abcdef";
private static void HexEncodeUtf16CodeUnit(TextWriter writer, char c)
{
writer.Write("\\u");
writer.Write(Hex[(c >> 12) & 0xf]);
writer.Write(Hex[(c >> 8) & 0xf]);
writer.Write(Hex[(c >> 4) & 0xf]);
writer.Write(Hex[(c >> 0) & 0xf]);
}
/// <summary>
/// Settings controlling JSON formatting.
/// </summary>
public sealed class Settings
{
/// <summary>
/// Default settings, as used by <see cref="JsonFormatter.Default"/>
/// </summary>
public static Settings Default { get; }
// Workaround for the Mono compiler complaining about XML comments not being on
// valid language elements.
static Settings()
{
Default = new Settings(false);
}
/// <summary>
/// Whether fields which would otherwise not be included in the formatted data
/// should be formatted even when the value is not present, or has the default value.
/// This option only affects fields which don't support "presence" (e.g.
/// singular non-optional proto3 primitive fields).
/// </summary>
public bool FormatDefaultValues { get; }
/// <summary>
/// The type registry used to format <see cref="Any"/> messages.
/// </summary>
public TypeRegistry TypeRegistry { get; }
/// <summary>
/// Whether to format enums as ints. Defaults to false.
/// </summary>
public bool FormatEnumsAsIntegers { get; }
/// <summary>
/// Creates a new <see cref="Settings"/> object with the specified formatting of default values
/// and an empty type registry.
/// </summary>
/// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param>
public Settings(bool formatDefaultValues) : this(formatDefaultValues, TypeRegistry.Empty)
{
}
/// <summary>
/// Creates a new <see cref="Settings"/> object with the specified formatting of default values
/// and type registry.
/// </summary>
/// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param>
/// <param name="typeRegistry">The <see cref="TypeRegistry"/> to use when formatting <see cref="Any"/> messages.</param>
public Settings(bool formatDefaultValues, TypeRegistry typeRegistry) : this(formatDefaultValues, typeRegistry, false)
{
}
/// <summary>
/// Creates a new <see cref="Settings"/> object with the specified parameters.
/// </summary>
/// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param>
/// <param name="typeRegistry">The <see cref="TypeRegistry"/> to use when formatting <see cref="Any"/> messages. TypeRegistry.Empty will be used if it is null.</param>
/// <param name="formatEnumsAsIntegers"><c>true</c> to format the enums as integers; <c>false</c> to format enums as enum names.</param>
private Settings(bool formatDefaultValues,
TypeRegistry typeRegistry,
bool formatEnumsAsIntegers)
{
FormatDefaultValues = formatDefaultValues;
TypeRegistry = typeRegistry ?? TypeRegistry.Empty;
FormatEnumsAsIntegers = formatEnumsAsIntegers;
}
/// <summary>
/// Creates a new <see cref="Settings"/> object with the specified formatting of default values and the current settings.
/// </summary>
/// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param>
public Settings WithFormatDefaultValues(bool formatDefaultValues) => new Settings(formatDefaultValues, TypeRegistry, FormatEnumsAsIntegers);
/// <summary>
/// Creates a new <see cref="Settings"/> object with the specified type registry and the current settings.
/// </summary>
/// <param name="typeRegistry">The <see cref="TypeRegistry"/> to use when formatting <see cref="Any"/> messages.</param>
public Settings WithTypeRegistry(TypeRegistry typeRegistry) => new Settings(FormatDefaultValues, typeRegistry, FormatEnumsAsIntegers);
/// <summary>
/// Creates a new <see cref="Settings"/> object with the specified enums formatting option and the current settings.
/// </summary>
/// <param name="formatEnumsAsIntegers"><c>true</c> to format the enums as integers; <c>false</c> to format enums as enum names.</param>
public Settings WithFormatEnumsAsIntegers(bool formatEnumsAsIntegers) => new Settings(FormatDefaultValues, TypeRegistry, formatEnumsAsIntegers);
}
// Effectively a cache of mapping from enum values to the original name as specified in the proto file,
// fetched by reflection.
// The need for this is unfortunate, as is its unbounded size, but realistically it shouldn't cause issues.
private static class OriginalEnumValueHelper
{
// TODO: In the future we might want to use ConcurrentDictionary, at the point where all
// the platforms we target have it.
private static readonly Dictionary<System.Type, Dictionary<object, string>> dictionaries
= new Dictionary<System.Type, Dictionary<object, string>>();
internal static string GetOriginalName(object value)
{
var enumType = value.GetType();
Dictionary<object, string> nameMapping;
lock (dictionaries)
{
if (!dictionaries.TryGetValue(enumType, out nameMapping))
{
nameMapping = GetNameMapping(enumType);
dictionaries[enumType] = nameMapping;
}
}
string originalName;
// If this returns false, originalName will be null, which is what we want.
nameMapping.TryGetValue(value, out originalName);
return originalName;
}
#if NET35
// TODO: Consider adding functionality to TypeExtensions to avoid this difference.
private static Dictionary<object, string> GetNameMapping(System.Type enumType) =>
enumType.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)
.Where(f => (f.GetCustomAttributes(typeof(OriginalNameAttribute), false)
.FirstOrDefault() as OriginalNameAttribute)
?.PreferredAlias ?? true)
.ToDictionary(f => f.GetValue(null),
f => (f.GetCustomAttributes(typeof(OriginalNameAttribute), false)
.FirstOrDefault() as OriginalNameAttribute)
// If the attribute hasn't been applied, fall back to the name of the field.
?.Name ?? f.Name);
#else
private static Dictionary<object, string> GetNameMapping(System.Type enumType) =>
enumType.GetTypeInfo().DeclaredFields
.Where(f => f.IsStatic)
.Where(f => f.GetCustomAttributes<OriginalNameAttribute>()
.FirstOrDefault()?.PreferredAlias ?? true)
.ToDictionary(f => f.GetValue(null),
f => f.GetCustomAttributes<OriginalNameAttribute>()
.FirstOrDefault()
// If the attribute hasn't been applied, fall back to the name of the field.
?.Name ?? f.Name);
#endif
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,166 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
namespace LC.Google.Protobuf
{
internal sealed class JsonToken : IEquatable<JsonToken>
{
// Tokens with no value can be reused.
private static readonly JsonToken _true = new JsonToken(TokenType.True);
private static readonly JsonToken _false = new JsonToken(TokenType.False);
private static readonly JsonToken _null = new JsonToken(TokenType.Null);
private static readonly JsonToken startObject = new JsonToken(TokenType.StartObject);
private static readonly JsonToken endObject = new JsonToken(TokenType.EndObject);
private static readonly JsonToken startArray = new JsonToken(TokenType.StartArray);
private static readonly JsonToken endArray = new JsonToken(TokenType.EndArray);
private static readonly JsonToken endDocument = new JsonToken(TokenType.EndDocument);
internal static JsonToken Null { get { return _null; } }
internal static JsonToken False { get { return _false; } }
internal static JsonToken True { get { return _true; } }
internal static JsonToken StartObject{ get { return startObject; } }
internal static JsonToken EndObject { get { return endObject; } }
internal static JsonToken StartArray { get { return startArray; } }
internal static JsonToken EndArray { get { return endArray; } }
internal static JsonToken EndDocument { get { return endDocument; } }
internal static JsonToken Name(string name)
{
return new JsonToken(TokenType.Name, stringValue: name);
}
internal static JsonToken Value(string value)
{
return new JsonToken(TokenType.StringValue, stringValue: value);
}
internal static JsonToken Value(double value)
{
return new JsonToken(TokenType.Number, numberValue: value);
}
internal enum TokenType
{
Null,
False,
True,
StringValue,
Number,
Name,
StartObject,
EndObject,
StartArray,
EndArray,
EndDocument
}
// A value is a string, number, array, object, null, true or false
// Arrays and objects have start/end
// A document consists of a value
// Objects are name/value sequences.
private readonly TokenType type;
private readonly string stringValue;
private readonly double numberValue;
internal TokenType Type { get { return type; } }
internal string StringValue { get { return stringValue; } }
internal double NumberValue { get { return numberValue; } }
private JsonToken(TokenType type, string stringValue = null, double numberValue = 0)
{
this.type = type;
this.stringValue = stringValue;
this.numberValue = numberValue;
}
public override bool Equals(object obj)
{
return Equals(obj as JsonToken);
}
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = hash * 31 + (int) type;
hash = hash * 31 + stringValue == null ? 0 : stringValue.GetHashCode();
hash = hash * 31 + numberValue.GetHashCode();
return hash;
}
}
public override string ToString()
{
switch (type)
{
case TokenType.Null:
return "null";
case TokenType.True:
return "true";
case TokenType.False:
return "false";
case TokenType.Name:
return "name (" + stringValue + ")";
case TokenType.StringValue:
return "value (" + stringValue + ")";
case TokenType.Number:
return "number (" + numberValue + ")";
case TokenType.StartObject:
return "start-object";
case TokenType.EndObject:
return "end-object";
case TokenType.StartArray:
return "start-array";
case TokenType.EndArray:
return "end-array";
case TokenType.EndDocument:
return "end-document";
default:
throw new InvalidOperationException("Token is of unknown type " + type);
}
}
public bool Equals(JsonToken other)
{
if (ReferenceEquals(other, null))
{
return false;
}
// Note use of other.numberValue.Equals rather than ==, so that NaN compares appropriately.
return other.type == type && other.stringValue == stringValue && other.numberValue.Equals(numberValue);
}
}
}

View File

@ -0,0 +1,766 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
namespace LC.Google.Protobuf
{
/// <summary>
/// Simple but strict JSON tokenizer, rigidly following RFC 7159.
/// </summary>
/// <remarks>
/// <para>
/// This tokenizer is stateful, and only returns "useful" tokens - names, values etc.
/// It does not create tokens for the separator between names and values, or for the comma
/// between values. It validates the token stream as it goes - so callers can assume that the
/// tokens it produces are appropriate. For example, it would never produce "start object, end array."
/// </para>
/// <para>Implementation details: the base class handles single token push-back and </para>
/// <para>Not thread-safe.</para>
/// </remarks>
internal abstract class JsonTokenizer
{
private JsonToken bufferedToken;
/// <summary>
/// Creates a tokenizer that reads from the given text reader.
/// </summary>
internal static JsonTokenizer FromTextReader(TextReader reader)
{
return new JsonTextTokenizer(reader);
}
/// <summary>
/// Creates a tokenizer that first replays the given list of tokens, then continues reading
/// from another tokenizer. Note that if the returned tokenizer is "pushed back", that does not push back
/// on the continuation tokenizer, or vice versa. Care should be taken when using this method - it was
/// created for the sake of Any parsing.
/// </summary>
internal static JsonTokenizer FromReplayedTokens(IList<JsonToken> tokens, JsonTokenizer continuation)
{
return new JsonReplayTokenizer(tokens, continuation);
}
/// <summary>
/// Returns the depth of the stack, purely in objects (not collections).
/// Informally, this is the number of remaining unclosed '{' characters we have.
/// </summary>
internal int ObjectDepth { get; private set; }
// TODO: Why do we allow a different token to be pushed back? It might be better to always remember the previous
// token returned, and allow a parameterless Rewind() method (which could only be called once, just like the current PushBack).
internal void PushBack(JsonToken token)
{
if (bufferedToken != null)
{
throw new InvalidOperationException("Can't push back twice");
}
bufferedToken = token;
if (token.Type == JsonToken.TokenType.StartObject)
{
ObjectDepth--;
}
else if (token.Type == JsonToken.TokenType.EndObject)
{
ObjectDepth++;
}
}
/// <summary>
/// Returns the next JSON token in the stream. An EndDocument token is returned to indicate the end of the stream,
/// after which point <c>Next()</c> should not be called again.
/// </summary>
/// <remarks>This implementation provides single-token buffering, and calls <see cref="NextImpl"/> if there is no buffered token.</remarks>
/// <returns>The next token in the stream. This is never null.</returns>
/// <exception cref="InvalidOperationException">This method is called after an EndDocument token has been returned</exception>
/// <exception cref="InvalidJsonException">The input text does not comply with RFC 7159</exception>
internal JsonToken Next()
{
JsonToken tokenToReturn;
if (bufferedToken != null)
{
tokenToReturn = bufferedToken;
bufferedToken = null;
}
else
{
tokenToReturn = NextImpl();
}
if (tokenToReturn.Type == JsonToken.TokenType.StartObject)
{
ObjectDepth++;
}
else if (tokenToReturn.Type == JsonToken.TokenType.EndObject)
{
ObjectDepth--;
}
return tokenToReturn;
}
/// <summary>
/// Returns the next JSON token in the stream, when requested by the base class. (The <see cref="Next"/> method delegates
/// to this if it doesn't have a buffered token.)
/// </summary>
/// <exception cref="InvalidOperationException">This method is called after an EndDocument token has been returned</exception>
/// <exception cref="InvalidJsonException">The input text does not comply with RFC 7159</exception>
protected abstract JsonToken NextImpl();
/// <summary>
/// Skips the value we're about to read. This must only be called immediately after reading a property name.
/// If the value is an object or an array, the complete object/array is skipped.
/// </summary>
internal void SkipValue()
{
// We'll assume that Next() makes sure that the end objects and end arrays are all valid.
// All we care about is the total nesting depth we need to close.
int depth = 0;
// do/while rather than while loop so that we read at least one token.
do
{
var token = Next();
switch (token.Type)
{
case JsonToken.TokenType.EndArray:
case JsonToken.TokenType.EndObject:
depth--;
break;
case JsonToken.TokenType.StartArray:
case JsonToken.TokenType.StartObject:
depth++;
break;
}
} while (depth != 0);
}
/// <summary>
/// Tokenizer which first exhausts a list of tokens, then consults another tokenizer.
/// </summary>
private class JsonReplayTokenizer : JsonTokenizer
{
private readonly IList<JsonToken> tokens;
private readonly JsonTokenizer nextTokenizer;
private int nextTokenIndex;
internal JsonReplayTokenizer(IList<JsonToken> tokens, JsonTokenizer nextTokenizer)
{
this.tokens = tokens;
this.nextTokenizer = nextTokenizer;
}
// FIXME: Object depth not maintained...
protected override JsonToken NextImpl()
{
if (nextTokenIndex >= tokens.Count)
{
return nextTokenizer.Next();
}
return tokens[nextTokenIndex++];
}
}
/// <summary>
/// Tokenizer which does all the *real* work of parsing JSON.
/// </summary>
private sealed class JsonTextTokenizer : JsonTokenizer
{
// The set of states in which a value is valid next token.
private static readonly State ValueStates = State.ArrayStart | State.ArrayAfterComma | State.ObjectAfterColon | State.StartOfDocument;
private readonly Stack<ContainerType> containerStack = new Stack<ContainerType>();
private readonly PushBackReader reader;
private State state;
internal JsonTextTokenizer(TextReader reader)
{
this.reader = new PushBackReader(reader);
state = State.StartOfDocument;
containerStack.Push(ContainerType.Document);
}
/// <remarks>
/// This method essentially just loops through characters skipping whitespace, validating and
/// changing state (e.g. from ObjectBeforeColon to ObjectAfterColon)
/// until it reaches something which will be a genuine token (e.g. a start object, or a value) at which point
/// it returns the token. Although the method is large, it would be relatively hard to break down further... most
/// of it is the large switch statement, which sometimes returns and sometimes doesn't.
/// </remarks>
protected override JsonToken NextImpl()
{
if (state == State.ReaderExhausted)
{
throw new InvalidOperationException("Next() called after end of document");
}
while (true)
{
var next = reader.Read();
if (next == null)
{
ValidateState(State.ExpectedEndOfDocument, "Unexpected end of document in state: ");
state = State.ReaderExhausted;
return JsonToken.EndDocument;
}
switch (next.Value)
{
// Skip whitespace between tokens
case ' ':
case '\t':
case '\r':
case '\n':
break;
case ':':
ValidateState(State.ObjectBeforeColon, "Invalid state to read a colon: ");
state = State.ObjectAfterColon;
break;
case ',':
ValidateState(State.ObjectAfterProperty | State.ArrayAfterValue, "Invalid state to read a comma: ");
state = state == State.ObjectAfterProperty ? State.ObjectAfterComma : State.ArrayAfterComma;
break;
case '"':
string stringValue = ReadString();
if ((state & (State.ObjectStart | State.ObjectAfterComma)) != 0)
{
state = State.ObjectBeforeColon;
return JsonToken.Name(stringValue);
}
else
{
ValidateAndModifyStateForValue("Invalid state to read a double quote: ");
return JsonToken.Value(stringValue);
}
case '{':
ValidateState(ValueStates, "Invalid state to read an open brace: ");
state = State.ObjectStart;
containerStack.Push(ContainerType.Object);
return JsonToken.StartObject;
case '}':
ValidateState(State.ObjectAfterProperty | State.ObjectStart, "Invalid state to read a close brace: ");
PopContainer();
return JsonToken.EndObject;
case '[':
ValidateState(ValueStates, "Invalid state to read an open square bracket: ");
state = State.ArrayStart;
containerStack.Push(ContainerType.Array);
return JsonToken.StartArray;
case ']':
ValidateState(State.ArrayAfterValue | State.ArrayStart, "Invalid state to read a close square bracket: ");
PopContainer();
return JsonToken.EndArray;
case 'n': // Start of null
ConsumeLiteral("null");
ValidateAndModifyStateForValue("Invalid state to read a null literal: ");
return JsonToken.Null;
case 't': // Start of true
ConsumeLiteral("true");
ValidateAndModifyStateForValue("Invalid state to read a true literal: ");
return JsonToken.True;
case 'f': // Start of false
ConsumeLiteral("false");
ValidateAndModifyStateForValue("Invalid state to read a false literal: ");
return JsonToken.False;
case '-': // Start of a number
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
double number = ReadNumber(next.Value);
ValidateAndModifyStateForValue("Invalid state to read a number token: ");
return JsonToken.Value(number);
default:
throw new InvalidJsonException("Invalid first character of token: " + next.Value);
}
}
}
private void ValidateState(State validStates, string errorPrefix)
{
if ((validStates & state) == 0)
{
throw reader.CreateException(errorPrefix + state);
}
}
/// <summary>
/// Reads a string token. It is assumed that the opening " has already been read.
/// </summary>
private string ReadString()
{
var value = new StringBuilder();
bool haveHighSurrogate = false;
while (true)
{
char c = reader.ReadOrFail("Unexpected end of text while reading string");
if (c < ' ')
{
throw reader.CreateException(string.Format(CultureInfo.InvariantCulture, "Invalid character in string literal: U+{0:x4}", (int) c));
}
if (c == '"')
{
if (haveHighSurrogate)
{
throw reader.CreateException("Invalid use of surrogate pair code units");
}
return value.ToString();
}
if (c == '\\')
{
c = ReadEscapedCharacter();
}
// TODO: Consider only allowing surrogate pairs that are either both escaped,
// or both not escaped. It would be a very odd text stream that contained a "lone" high surrogate
// followed by an escaped low surrogate or vice versa... and that couldn't even be represented in UTF-8.
if (haveHighSurrogate != char.IsLowSurrogate(c))
{
throw reader.CreateException("Invalid use of surrogate pair code units");
}
haveHighSurrogate = char.IsHighSurrogate(c);
value.Append(c);
}
}
/// <summary>
/// Reads an escaped character. It is assumed that the leading backslash has already been read.
/// </summary>
private char ReadEscapedCharacter()
{
char c = reader.ReadOrFail("Unexpected end of text while reading character escape sequence");
switch (c)
{
case 'n':
return '\n';
case '\\':
return '\\';
case 'b':
return '\b';
case 'f':
return '\f';
case 'r':
return '\r';
case 't':
return '\t';
case '"':
return '"';
case '/':
return '/';
case 'u':
return ReadUnicodeEscape();
default:
throw reader.CreateException(string.Format(CultureInfo.InvariantCulture, "Invalid character in character escape sequence: U+{0:x4}", (int) c));
}
}
/// <summary>
/// Reads an escaped Unicode 4-nybble hex sequence. It is assumed that the leading \u has already been read.
/// </summary>
private char ReadUnicodeEscape()
{
int result = 0;
for (int i = 0; i < 4; i++)
{
char c = reader.ReadOrFail("Unexpected end of text while reading Unicode escape sequence");
int nybble;
if (c >= '0' && c <= '9')
{
nybble = c - '0';
}
else if (c >= 'a' && c <= 'f')
{
nybble = c - 'a' + 10;
}
else if (c >= 'A' && c <= 'F')
{
nybble = c - 'A' + 10;
}
else
{
throw reader.CreateException(string.Format(CultureInfo.InvariantCulture, "Invalid character in character escape sequence: U+{0:x4}", (int) c));
}
result = (result << 4) + nybble;
}
return (char) result;
}
/// <summary>
/// Consumes a text-only literal, throwing an exception if the read text doesn't match it.
/// It is assumed that the first letter of the literal has already been read.
/// </summary>
private void ConsumeLiteral(string text)
{
for (int i = 1; i < text.Length; i++)
{
char? next = reader.Read();
if (next == null)
{
throw reader.CreateException("Unexpected end of text while reading literal token " + text);
}
if (next.Value != text[i])
{
throw reader.CreateException("Unexpected character while reading literal token " + text);
}
}
}
private double ReadNumber(char initialCharacter)
{
StringBuilder builder = new StringBuilder();
if (initialCharacter == '-')
{
builder.Append("-");
}
else
{
reader.PushBack(initialCharacter);
}
// Each method returns the character it read that doesn't belong in that part,
// so we know what to do next, including pushing the character back at the end.
// null is returned for "end of text".
char? next = ReadInt(builder);
if (next == '.')
{
next = ReadFrac(builder);
}
if (next == 'e' || next == 'E')
{
next = ReadExp(builder);
}
// If we read a character which wasn't part of the number, push it back so we can read it again
// to parse the next token.
if (next != null)
{
reader.PushBack(next.Value);
}
// TODO: What exception should we throw if the value can't be represented as a double?
try
{
return double.Parse(builder.ToString(),
NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent,
CultureInfo.InvariantCulture);
}
catch (OverflowException)
{
throw reader.CreateException("Numeric value out of range: " + builder);
}
}
private char? ReadInt(StringBuilder builder)
{
char first = reader.ReadOrFail("Invalid numeric literal");
if (first < '0' || first > '9')
{
throw reader.CreateException("Invalid numeric literal");
}
builder.Append(first);
int digitCount;
char? next = ConsumeDigits(builder, out digitCount);
if (first == '0' && digitCount != 0)
{
throw reader.CreateException("Invalid numeric literal: leading 0 for non-zero value.");
}
return next;
}
private char? ReadFrac(StringBuilder builder)
{
builder.Append('.'); // Already consumed this
int digitCount;
char? next = ConsumeDigits(builder, out digitCount);
if (digitCount == 0)
{
throw reader.CreateException("Invalid numeric literal: fraction with no trailing digits");
}
return next;
}
private char? ReadExp(StringBuilder builder)
{
builder.Append('E'); // Already consumed this (or 'e')
char? next = reader.Read();
if (next == null)
{
throw reader.CreateException("Invalid numeric literal: exponent with no trailing digits");
}
if (next == '-' || next == '+')
{
builder.Append(next.Value);
}
else
{
reader.PushBack(next.Value);
}
int digitCount;
next = ConsumeDigits(builder, out digitCount);
if (digitCount == 0)
{
throw reader.CreateException("Invalid numeric literal: exponent without value");
}
return next;
}
private char? ConsumeDigits(StringBuilder builder, out int count)
{
count = 0;
while (true)
{
char? next = reader.Read();
if (next == null || next.Value < '0' || next.Value > '9')
{
return next;
}
count++;
builder.Append(next.Value);
}
}
/// <summary>
/// Validates that we're in a valid state to read a value (using the given error prefix if necessary)
/// and changes the state to the appropriate one, e.g. ObjectAfterColon to ObjectAfterProperty.
/// </summary>
private void ValidateAndModifyStateForValue(string errorPrefix)
{
ValidateState(ValueStates, errorPrefix);
switch (state)
{
case State.StartOfDocument:
state = State.ExpectedEndOfDocument;
return;
case State.ObjectAfterColon:
state = State.ObjectAfterProperty;
return;
case State.ArrayStart:
case State.ArrayAfterComma:
state = State.ArrayAfterValue;
return;
default:
throw new InvalidOperationException("ValidateAndModifyStateForValue does not handle all value states (and should)");
}
}
/// <summary>
/// Pops the top-most container, and sets the state to the appropriate one for the end of a value
/// in the parent container.
/// </summary>
private void PopContainer()
{
containerStack.Pop();
var parent = containerStack.Peek();
switch (parent)
{
case ContainerType.Object:
state = State.ObjectAfterProperty;
break;
case ContainerType.Array:
state = State.ArrayAfterValue;
break;
case ContainerType.Document:
state = State.ExpectedEndOfDocument;
break;
default:
throw new InvalidOperationException("Unexpected container type: " + parent);
}
}
private enum ContainerType
{
Document, Object, Array
}
/// <summary>
/// Possible states of the tokenizer.
/// </summary>
/// <remarks>
/// <para>This is a flags enum purely so we can simply and efficiently represent a set of valid states
/// for checking.</para>
/// <para>
/// Each is documented with an example,
/// where ^ represents the current position within the text stream. The examples all use string values,
/// but could be any value, including nested objects/arrays.
/// The complete state of the tokenizer also includes a stack to indicate the contexts (arrays/objects).
/// Any additional notional state of "AfterValue" indicates that a value has been completed, at which
/// point there's an immediate transition to ExpectedEndOfDocument, ObjectAfterProperty or ArrayAfterValue.
/// </para>
/// <para>
/// These states were derived manually by reading RFC 7159 carefully.
/// </para>
/// </remarks>
[Flags]
private enum State
{
/// <summary>
/// ^ { "foo": "bar" }
/// Before the value in a document. Next states: ObjectStart, ArrayStart, "AfterValue"
/// </summary>
StartOfDocument = 1 << 0,
/// <summary>
/// { "foo": "bar" } ^
/// After the value in a document. Next states: ReaderExhausted
/// </summary>
ExpectedEndOfDocument = 1 << 1,
/// <summary>
/// { "foo": "bar" } ^ (and already read to the end of the reader)
/// Terminal state.
/// </summary>
ReaderExhausted = 1 << 2,
/// <summary>
/// { ^ "foo": "bar" }
/// Before the *first* property in an object.
/// Next states:
/// "AfterValue" (empty object)
/// ObjectBeforeColon (read a name)
/// </summary>
ObjectStart = 1 << 3,
/// <summary>
/// { "foo" ^ : "bar", "x": "y" }
/// Next state: ObjectAfterColon
/// </summary>
ObjectBeforeColon = 1 << 4,
/// <summary>
/// { "foo" : ^ "bar", "x": "y" }
/// Before any property other than the first in an object.
/// (Equivalently: after any property in an object)
/// Next states:
/// "AfterValue" (value is simple)
/// ObjectStart (value is object)
/// ArrayStart (value is array)
/// </summary>
ObjectAfterColon = 1 << 5,
/// <summary>
/// { "foo" : "bar" ^ , "x" : "y" }
/// At the end of a property, so expecting either a comma or end-of-object
/// Next states: ObjectAfterComma or "AfterValue"
/// </summary>
ObjectAfterProperty = 1 << 6,
/// <summary>
/// { "foo":"bar", ^ "x":"y" }
/// Read the comma after the previous property, so expecting another property.
/// This is like ObjectStart, but closing brace isn't valid here
/// Next state: ObjectBeforeColon.
/// </summary>
ObjectAfterComma = 1 << 7,
/// <summary>
/// [ ^ "foo", "bar" ]
/// Before the *first* value in an array.
/// Next states:
/// "AfterValue" (read a value)
/// "AfterValue" (end of array; will pop stack)
/// </summary>
ArrayStart = 1 << 8,
/// <summary>
/// [ "foo" ^ , "bar" ]
/// After any value in an array, so expecting either a comma or end-of-array
/// Next states: ArrayAfterComma or "AfterValue"
/// </summary>
ArrayAfterValue = 1 << 9,
/// <summary>
/// [ "foo", ^ "bar" ]
/// After a comma in an array, so there *must* be another value (simple or complex).
/// Next states: "AfterValue" (simple value), StartObject, StartArray
/// </summary>
ArrayAfterComma = 1 << 10
}
/// <summary>
/// Wrapper around a text reader allowing small amounts of buffering and location handling.
/// </summary>
private class PushBackReader
{
// TODO: Add locations for errors etc.
private readonly TextReader reader;
internal PushBackReader(TextReader reader)
{
// TODO: Wrap the reader in a BufferedReader?
this.reader = reader;
}
/// <summary>
/// The buffered next character, if we have one.
/// </summary>
private char? nextChar;
/// <summary>
/// Returns the next character in the stream, or null if we have reached the end.
/// </summary>
/// <returns></returns>
internal char? Read()
{
if (nextChar != null)
{
char? tmp = nextChar;
nextChar = null;
return tmp;
}
int next = reader.Read();
return next == -1 ? null : (char?) next;
}
internal char ReadOrFail(string messageOnFailure)
{
char? next = Read();
if (next == null)
{
throw CreateException(messageOnFailure);
}
return next.Value;
}
internal void PushBack(char c)
{
if (nextChar != null)
{
throw new InvalidOperationException("Cannot push back when already buffering a character");
}
nextChar = c;
}
/// <summary>
/// Creates a new exception appropriate for the current state of the reader.
/// </summary>
internal InvalidJsonException CreateException(string message)
{
// TODO: Keep track of and use the location.
return new InvalidJsonException(message);
}
}
}
}
}

View File

@ -0,0 +1,46 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>C# runtime library for Protocol Buffers - Google's data interchange format.</Description>
<Copyright>Copyright 2015, Google Inc.</Copyright>
<AssemblyTitle>Google Protocol Buffers</AssemblyTitle>
<VersionPrefix>3.14.0</VersionPrefix>
<!-- C# 7.2 is required for Span/BufferWriter/ReadOnlySequence -->
<LangVersion>7.2</LangVersion>
<Authors>Google Inc.</Authors>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyOriginatorKeyFile>./keys/Google.Protobuf.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<PackageTags>Protocol;Buffers;Binary;Serialization;Format;Google;proto;proto3</PackageTags>
<PackageReleaseNotes>C# proto3 support</PackageReleaseNotes>
<PackageProjectUrl>https://github.com/protocolbuffers/protobuf</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/protocolbuffers/protobuf/blob/master/LICENSE</PackageLicenseUrl>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/protocolbuffers/protobuf.git</RepositoryUrl>
<DefineConstants>$(DefineConstants);GOOGLE_PROTOBUF_SUPPORT_SYSTEM_MEMORY</DefineConstants>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<!-- Include PDB in the built .nupkg -->
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<ReleaseVersion>0.7.10</ReleaseVersion>
<PackageId>LC.Google.Protobuf</PackageId>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<DefineConstants>$(DefineConstants);GOOGLE_PROTOBUF_SUPPORT_FAST_STRING</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Memory" Version="4.5.3" />
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="1.0.0" />
<!-- Needed for the net45 build to work on Unix. See https://github.com/dotnet/designs/pull/33 -->
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" PrivateAssets="All" Version="1.0.0" />
</ItemGroup>
<!-- Needed for netcoreapp2.1 to work correctly. .NET is not able to load the assembly without this -->
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.2" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,110 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.IO;
namespace LC.Google.Protobuf
{
/// <summary>
/// Stream implementation which proxies another stream, only allowing a certain amount
/// of data to be read. Note that this is only used to read delimited streams, so it
/// doesn't attempt to implement everything.
/// </summary>
internal sealed class LimitedInputStream : Stream
{
private readonly Stream proxied;
private int bytesLeft;
internal LimitedInputStream(Stream proxied, int size)
{
this.proxied = proxied;
bytesLeft = size;
}
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return false; }
}
public override void Flush()
{
}
public override long Length
{
get { throw new NotSupportedException(); }
}
public override long Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
public override int Read(byte[] buffer, int offset, int count)
{
if (bytesLeft > 0)
{
int bytesRead = proxied.Read(buffer, offset, Math.Min(bytesLeft, count));
bytesLeft -= bytesRead;
return bytesRead;
}
return 0;
}
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();
}
}
}

View File

@ -0,0 +1,306 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using LC.Google.Protobuf.Reflection;
using System.Buffers;
using System.Collections;
using System;
using System.IO;
using System.Linq;
using System.Security;
namespace LC.Google.Protobuf
{
/// <summary>
/// Extension methods on <see cref="IMessage"/> and <see cref="IMessage{T}"/>.
/// </summary>
public static class MessageExtensions
{
/// <summary>
/// Merges data from the given byte array into an existing message.
/// </summary>
/// <param name="message">The message to merge the data into.</param>
/// <param name="data">The data to merge, which must be protobuf-encoded binary data.</param>
public static void MergeFrom(this IMessage message, byte[] data) =>
MergeFrom(message, data, false, null);
/// <summary>
/// Merges data from the given byte array slice into an existing message.
/// </summary>
/// <param name="message">The message to merge the data into.</param>
/// <param name="data">The data containing the slice to merge, which must be protobuf-encoded binary data.</param>
/// <param name="offset">The offset of the slice to merge.</param>
/// <param name="length">The length of the slice to merge.</param>
public static void MergeFrom(this IMessage message, byte[] data, int offset, int length) =>
MergeFrom(message, data, offset, length, false, null);
/// <summary>
/// Merges data from the given byte string into an existing message.
/// </summary>
/// <param name="message">The message to merge the data into.</param>
/// <param name="data">The data to merge, which must be protobuf-encoded binary data.</param>
public static void MergeFrom(this IMessage message, ByteString data) =>
MergeFrom(message, data, false, null);
/// <summary>
/// Merges data from the given stream into an existing message.
/// </summary>
/// <param name="message">The message to merge the data into.</param>
/// <param name="input">Stream containing the data to merge, which must be protobuf-encoded binary data.</param>
public static void MergeFrom(this IMessage message, Stream input) =>
MergeFrom(message, input, false, null);
/// <summary>
/// Merges length-delimited data from the given stream into an existing message.
/// </summary>
/// <remarks>
/// The stream is expected to contain a length and then the data. Only the amount of data
/// specified by the length will be consumed.
/// </remarks>
/// <param name="message">The message to merge the data into.</param>
/// <param name="input">Stream containing the data to merge, which must be protobuf-encoded binary data.</param>
public static void MergeDelimitedFrom(this IMessage message, Stream input) =>
MergeDelimitedFrom(message, input, false, null);
/// <summary>
/// Converts the given message into a byte array in protobuf encoding.
/// </summary>
/// <param name="message">The message to convert.</param>
/// <returns>The message data as a byte array.</returns>
public static byte[] ToByteArray(this IMessage message)
{
ProtoPreconditions.CheckNotNull(message, "message");
byte[] result = new byte[message.CalculateSize()];
CodedOutputStream output = new CodedOutputStream(result);
message.WriteTo(output);
output.CheckNoSpaceLeft();
return result;
}
/// <summary>
/// Writes the given message data to the given stream in protobuf encoding.
/// </summary>
/// <param name="message">The message to write to the stream.</param>
/// <param name="output">The stream to write to.</param>
public static void WriteTo(this IMessage message, Stream output)
{
ProtoPreconditions.CheckNotNull(message, "message");
ProtoPreconditions.CheckNotNull(output, "output");
CodedOutputStream codedOutput = new CodedOutputStream(output);
message.WriteTo(codedOutput);
codedOutput.Flush();
}
/// <summary>
/// Writes the length and then data of the given message to a stream.
/// </summary>
/// <param name="message">The message to write.</param>
/// <param name="output">The output stream to write to.</param>
public static void WriteDelimitedTo(this IMessage message, Stream output)
{
ProtoPreconditions.CheckNotNull(message, "message");
ProtoPreconditions.CheckNotNull(output, "output");
CodedOutputStream codedOutput = new CodedOutputStream(output);
codedOutput.WriteLength(message.CalculateSize());
message.WriteTo(codedOutput);
codedOutput.Flush();
}
/// <summary>
/// Converts the given message into a byte string in protobuf encoding.
/// </summary>
/// <param name="message">The message to convert.</param>
/// <returns>The message data as a byte string.</returns>
public static ByteString ToByteString(this IMessage message)
{
ProtoPreconditions.CheckNotNull(message, "message");
return ByteString.AttachBytes(message.ToByteArray());
}
/// <summary>
/// Writes the given message data to the given buffer writer in protobuf encoding.
/// </summary>
/// <param name="message">The message to write to the stream.</param>
/// <param name="output">The stream to write to.</param>
[SecuritySafeCritical]
public static void WriteTo(this IMessage message, IBufferWriter<byte> output)
{
ProtoPreconditions.CheckNotNull(message, nameof(message));
ProtoPreconditions.CheckNotNull(output, nameof(output));
WriteContext.Initialize(output, out WriteContext ctx);
WritingPrimitivesMessages.WriteRawMessage(ref ctx, message);
ctx.Flush();
}
/// <summary>
/// Writes the given message data to the given span in protobuf encoding.
/// The size of the destination span needs to fit the serialized size
/// of the message exactly, otherwise an exception is thrown.
/// </summary>
/// <param name="message">The message to write to the stream.</param>
/// <param name="output">The span to write to. Size must match size of the message exactly.</param>
[SecuritySafeCritical]
public static void WriteTo(this IMessage message, Span<byte> output)
{
ProtoPreconditions.CheckNotNull(message, nameof(message));
WriteContext.Initialize(ref output, out WriteContext ctx);
WritingPrimitivesMessages.WriteRawMessage(ref ctx, message);
ctx.CheckNoSpaceLeft();
}
/// <summary>
/// Checks if all required fields in a message have values set. For proto3 messages, this returns true
/// </summary>
public static bool IsInitialized(this IMessage message)
{
if (message.Descriptor.File.Syntax == Syntax.Proto3)
{
return true;
}
if (!message.Descriptor.IsExtensionsInitialized(message))
{
return false;
}
return message.Descriptor
.Fields
.InDeclarationOrder()
.All(f =>
{
if (f.IsMap)
{
var valueField = f.MessageType.Fields[2];
if (valueField.FieldType == FieldType.Message)
{
var map = (IDictionary)f.Accessor.GetValue(message);
return map.Values.Cast<IMessage>().All(IsInitialized);
}
else
{
return true;
}
}
else if (f.IsRepeated && f.FieldType == FieldType.Message || f.FieldType == FieldType.Group)
{
var enumerable = (IEnumerable)f.Accessor.GetValue(message);
return enumerable.Cast<IMessage>().All(IsInitialized);
}
else if (f.FieldType == FieldType.Message || f.FieldType == FieldType.Group)
{
if (f.Accessor.HasValue(message))
{
return ((IMessage)f.Accessor.GetValue(message)).IsInitialized();
}
else
{
return !f.IsRequired;
}
}
else if (f.IsRequired)
{
return f.Accessor.HasValue(message);
}
else
{
return true;
}
});
}
// Implementations allowing unknown fields to be discarded.
internal static void MergeFrom(this IMessage message, byte[] data, bool discardUnknownFields, ExtensionRegistry registry)
{
ProtoPreconditions.CheckNotNull(message, "message");
ProtoPreconditions.CheckNotNull(data, "data");
CodedInputStream input = new CodedInputStream(data);
input.DiscardUnknownFields = discardUnknownFields;
input.ExtensionRegistry = registry;
message.MergeFrom(input);
input.CheckReadEndOfStreamTag();
}
internal static void MergeFrom(this IMessage message, byte[] data, int offset, int length, bool discardUnknownFields, ExtensionRegistry registry)
{
ProtoPreconditions.CheckNotNull(message, "message");
ProtoPreconditions.CheckNotNull(data, "data");
CodedInputStream input = new CodedInputStream(data, offset, length);
input.DiscardUnknownFields = discardUnknownFields;
input.ExtensionRegistry = registry;
message.MergeFrom(input);
input.CheckReadEndOfStreamTag();
}
internal static void MergeFrom(this IMessage message, ByteString data, bool discardUnknownFields, ExtensionRegistry registry)
{
ProtoPreconditions.CheckNotNull(message, "message");
ProtoPreconditions.CheckNotNull(data, "data");
CodedInputStream input = data.CreateCodedInput();
input.DiscardUnknownFields = discardUnknownFields;
input.ExtensionRegistry = registry;
message.MergeFrom(input);
input.CheckReadEndOfStreamTag();
}
internal static void MergeFrom(this IMessage message, Stream input, bool discardUnknownFields, ExtensionRegistry registry)
{
ProtoPreconditions.CheckNotNull(message, "message");
ProtoPreconditions.CheckNotNull(input, "input");
CodedInputStream codedInput = new CodedInputStream(input);
codedInput.DiscardUnknownFields = discardUnknownFields;
codedInput.ExtensionRegistry = registry;
message.MergeFrom(codedInput);
codedInput.CheckReadEndOfStreamTag();
}
[SecuritySafeCritical]
internal static void MergeFrom(this IMessage message, ReadOnlySequence<byte> data, bool discardUnknownFields, ExtensionRegistry registry)
{
ParseContext.Initialize(data, out ParseContext ctx);
ctx.DiscardUnknownFields = discardUnknownFields;
ctx.ExtensionRegistry = registry;
ParsingPrimitivesMessages.ReadRawMessage(ref ctx, message);
ParsingPrimitivesMessages.CheckReadEndOfStreamTag(ref ctx.state);
}
internal static void MergeDelimitedFrom(this IMessage message, Stream input, bool discardUnknownFields, ExtensionRegistry registry)
{
ProtoPreconditions.CheckNotNull(message, "message");
ProtoPreconditions.CheckNotNull(input, "input");
int size = (int) CodedInputStream.ReadRawVarint32(input);
Stream limitedStream = new LimitedInputStream(input, size);
MergeFrom(message, limitedStream, discardUnknownFields, registry);
}
}
}

View File

@ -0,0 +1,376 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Buffers;
using System.IO;
using System.Security;
namespace LC.Google.Protobuf
{
/// <summary>
/// A general message parser, typically used by reflection-based code as all the methods
/// return simple <see cref="IMessage"/>.
/// </summary>
public class MessageParser
{
private Func<IMessage> factory;
// TODO: When we use a C# 7.1 compiler, make this private protected.
internal bool DiscardUnknownFields { get; }
internal ExtensionRegistry Extensions { get; }
internal MessageParser(Func<IMessage> factory, bool discardUnknownFields, ExtensionRegistry extensions)
{
this.factory = factory;
DiscardUnknownFields = discardUnknownFields;
Extensions = extensions;
}
/// <summary>
/// Creates a template instance ready for population.
/// </summary>
/// <returns>An empty message.</returns>
internal IMessage CreateTemplate()
{
return factory();
}
/// <summary>
/// Parses a message from a byte array.
/// </summary>
/// <param name="data">The byte array containing the message. Must not be null.</param>
/// <returns>The newly parsed message.</returns>
public IMessage ParseFrom(byte[] data)
{
IMessage message = factory();
message.MergeFrom(data, DiscardUnknownFields, Extensions);
return message;
}
/// <summary>
/// Parses a message from a byte array slice.
/// </summary>
/// <param name="data">The byte array containing the message. Must not be null.</param>
/// <param name="offset">The offset of the slice to parse.</param>
/// <param name="length">The length of the slice to parse.</param>
/// <returns>The newly parsed message.</returns>
public IMessage ParseFrom(byte[] data, int offset, int length)
{
IMessage message = factory();
message.MergeFrom(data, offset, length, DiscardUnknownFields, Extensions);
return message;
}
/// <summary>
/// Parses a message from the given byte string.
/// </summary>
/// <param name="data">The data to parse.</param>
/// <returns>The parsed message.</returns>
public IMessage ParseFrom(ByteString data)
{
IMessage message = factory();
message.MergeFrom(data, DiscardUnknownFields, Extensions);
return message;
}
/// <summary>
/// Parses a message from the given stream.
/// </summary>
/// <param name="input">The stream to parse.</param>
/// <returns>The parsed message.</returns>
public IMessage ParseFrom(Stream input)
{
IMessage message = factory();
message.MergeFrom(input, DiscardUnknownFields, Extensions);
return message;
}
/// <summary>
/// Parses a message from the given sequence.
/// </summary>
/// <param name="data">The data to parse.</param>
/// <returns>The parsed message.</returns>
[SecuritySafeCritical]
public IMessage ParseFrom(ReadOnlySequence<byte> data)
{
IMessage message = factory();
message.MergeFrom(data, DiscardUnknownFields, Extensions);
return message;
}
/// <summary>
/// Parses a length-delimited message from the given stream.
/// </summary>
/// <remarks>
/// The stream is expected to contain a length and then the data. Only the amount of data
/// specified by the length will be consumed.
/// </remarks>
/// <param name="input">The stream to parse.</param>
/// <returns>The parsed message.</returns>
public IMessage ParseDelimitedFrom(Stream input)
{
IMessage message = factory();
message.MergeDelimitedFrom(input, DiscardUnknownFields, Extensions);
return message;
}
/// <summary>
/// Parses a message from the given coded input stream.
/// </summary>
/// <param name="input">The stream to parse.</param>
/// <returns>The parsed message.</returns>
public IMessage ParseFrom(CodedInputStream input)
{
IMessage message = factory();
MergeFrom(message, input);
return message;
}
/// <summary>
/// Parses a message from the given JSON.
/// </summary>
/// <param name="json">The JSON to parse.</param>
/// <returns>The parsed message.</returns>
/// <exception cref="InvalidJsonException">The JSON does not comply with RFC 7159</exception>
/// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception>
public IMessage ParseJson(string json)
{
IMessage message = factory();
JsonParser.Default.Merge(message, json);
return message;
}
// TODO: When we're using a C# 7.1 compiler, make this private protected.
internal void MergeFrom(IMessage message, CodedInputStream codedInput)
{
bool originalDiscard = codedInput.DiscardUnknownFields;
try
{
codedInput.DiscardUnknownFields = DiscardUnknownFields;
message.MergeFrom(codedInput);
}
finally
{
codedInput.DiscardUnknownFields = originalDiscard;
}
}
/// <summary>
/// Creates a new message parser which optionally discards unknown fields when parsing.
/// </summary>
/// <param name="discardUnknownFields">Whether or not to discard unknown fields when parsing.</param>
/// <returns>A newly configured message parser.</returns>
public MessageParser WithDiscardUnknownFields(bool discardUnknownFields) =>
new MessageParser(factory, discardUnknownFields, Extensions);
/// <summary>
/// Creates a new message parser which registers extensions from the specified registry upon creating the message instance
/// </summary>
/// <param name="registry">The extensions to register</param>
/// <returns>A newly configured message parser.</returns>
public MessageParser WithExtensionRegistry(ExtensionRegistry registry) =>
new MessageParser(factory, DiscardUnknownFields, registry);
}
/// <summary>
/// A parser for a specific message type.
/// </summary>
/// <remarks>
/// <p>
/// This delegates most behavior to the
/// <see cref="IMessage.MergeFrom"/> implementation within the original type, but
/// provides convenient overloads to parse from a variety of sources.
/// </p>
/// <p>
/// Most applications will never need to create their own instances of this type;
/// instead, use the static <c>Parser</c> property of a generated message type to obtain a
/// parser for that type.
/// </p>
/// </remarks>
/// <typeparam name="T">The type of message to be parsed.</typeparam>
public sealed class MessageParser<T> : MessageParser where T : IMessage<T>
{
// Implementation note: all the methods here *could* just delegate up to the base class and cast the result.
// The current implementation avoids a virtual method call and a cast, which *may* be significant in some cases.
// Benchmarking work is required to measure the significance - but it's only a few lines of code in any case.
// The API wouldn't change anyway - just the implementation - so this work can be deferred.
private readonly Func<T> factory;
/// <summary>
/// Creates a new parser.
/// </summary>
/// <remarks>
/// The factory method is effectively an optimization over using a generic constraint
/// to require a parameterless constructor: delegates are significantly faster to execute.
/// </remarks>
/// <param name="factory">Function to invoke when a new, empty message is required.</param>
public MessageParser(Func<T> factory) : this(factory, false, null)
{
}
internal MessageParser(Func<T> factory, bool discardUnknownFields, ExtensionRegistry extensions) : base(() => factory(), discardUnknownFields, extensions)
{
this.factory = factory;
}
/// <summary>
/// Creates a template instance ready for population.
/// </summary>
/// <returns>An empty message.</returns>
internal new T CreateTemplate()
{
return factory();
}
/// <summary>
/// Parses a message from a byte array.
/// </summary>
/// <param name="data">The byte array containing the message. Must not be null.</param>
/// <returns>The newly parsed message.</returns>
public new T ParseFrom(byte[] data)
{
T message = factory();
message.MergeFrom(data, DiscardUnknownFields, Extensions);
return message;
}
/// <summary>
/// Parses a message from a byte array slice.
/// </summary>
/// <param name="data">The byte array containing the message. Must not be null.</param>
/// <param name="offset">The offset of the slice to parse.</param>
/// <param name="length">The length of the slice to parse.</param>
/// <returns>The newly parsed message.</returns>
public new T ParseFrom(byte[] data, int offset, int length)
{
T message = factory();
message.MergeFrom(data, offset, length, DiscardUnknownFields, Extensions);
return message;
}
/// <summary>
/// Parses a message from the given byte string.
/// </summary>
/// <param name="data">The data to parse.</param>
/// <returns>The parsed message.</returns>
public new T ParseFrom(ByteString data)
{
T message = factory();
message.MergeFrom(data, DiscardUnknownFields, Extensions);
return message;
}
/// <summary>
/// Parses a message from the given stream.
/// </summary>
/// <param name="input">The stream to parse.</param>
/// <returns>The parsed message.</returns>
public new T ParseFrom(Stream input)
{
T message = factory();
message.MergeFrom(input, DiscardUnknownFields, Extensions);
return message;
}
/// <summary>
/// Parses a message from the given sequence.
/// </summary>
/// <param name="data">The data to parse.</param>
/// <returns>The parsed message.</returns>
[SecuritySafeCritical]
public new T ParseFrom(ReadOnlySequence<byte> data)
{
T message = factory();
message.MergeFrom(data, DiscardUnknownFields, Extensions);
return message;
}
/// <summary>
/// Parses a length-delimited message from the given stream.
/// </summary>
/// <remarks>
/// The stream is expected to contain a length and then the data. Only the amount of data
/// specified by the length will be consumed.
/// </remarks>
/// <param name="input">The stream to parse.</param>
/// <returns>The parsed message.</returns>
public new T ParseDelimitedFrom(Stream input)
{
T message = factory();
message.MergeDelimitedFrom(input, DiscardUnknownFields, Extensions);
return message;
}
/// <summary>
/// Parses a message from the given coded input stream.
/// </summary>
/// <param name="input">The stream to parse.</param>
/// <returns>The parsed message.</returns>
public new T ParseFrom(CodedInputStream input)
{
T message = factory();
MergeFrom(message, input);
return message;
}
/// <summary>
/// Parses a message from the given JSON.
/// </summary>
/// <param name="json">The JSON to parse.</param>
/// <returns>The parsed message.</returns>
/// <exception cref="InvalidJsonException">The JSON does not comply with RFC 7159</exception>
/// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception>
public new T ParseJson(string json)
{
T message = factory();
JsonParser.Default.Merge(message, json);
return message;
}
/// <summary>
/// Creates a new message parser which optionally discards unknown fields when parsing.
/// </summary>
/// <param name="discardUnknownFields">Whether or not to discard unknown fields when parsing.</param>
/// <returns>A newly configured message parser.</returns>
public new MessageParser<T> WithDiscardUnknownFields(bool discardUnknownFields) =>
new MessageParser<T>(factory, discardUnknownFields, Extensions);
/// <summary>
/// Creates a new message parser which registers extensions from the specified registry upon creating the message instance
/// </summary>
/// <param name="registry">The extensions to register</param>
/// <returns>A newly configured message parser.</returns>
public new MessageParser<T> WithExtensionRegistry(ExtensionRegistry registry) =>
new MessageParser<T>(factory, DiscardUnknownFields, registry);
}
}

View File

@ -0,0 +1,40 @@
using System;
namespace LC.Google.Protobuf
{
/// <summary>
/// Struct used to hold the keys for the fieldByNumber table in DescriptorPool and the keys for the
/// extensionByNumber table in ExtensionRegistry.
/// </summary>
internal struct ObjectIntPair<T> : IEquatable<ObjectIntPair<T>> where T : class
{
private readonly int number;
private readonly T obj;
internal ObjectIntPair(T obj, int number)
{
this.number = number;
this.obj = obj;
}
public bool Equals(ObjectIntPair<T> other)
{
return obj == other.obj
&& number == other.number;
}
public override bool Equals(object obj)
{
if (obj is ObjectIntPair<T>)
{
return Equals((ObjectIntPair<T>)obj);
}
return false;
}
public override int GetHashCode()
{
return obj.GetHashCode() * ((1 << 16) - 1) + number;
}
}
}

View File

@ -0,0 +1,329 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using LC.Google.Protobuf.Collections;
namespace LC.Google.Protobuf
{
/// <summary>
/// An opaque struct that represents the current parsing state and is passed along
/// as the parsing proceeds.
/// All the public methods are intended to be invoked only by the generated code,
/// users should never invoke them directly.
/// </summary>
[SecuritySafeCritical]
public ref struct ParseContext
{
internal const int DefaultRecursionLimit = 100;
internal const int DefaultSizeLimit = Int32.MaxValue;
internal ReadOnlySpan<byte> buffer;
internal ParserInternalState state;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void Initialize(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, out ParseContext ctx)
{
ctx.buffer = buffer;
ctx.state = state;
}
/// <summary>
/// Creates a ParseContext instance from CodedInputStream.
/// WARNING: internally this copies the CodedInputStream's state, so after done with the ParseContext,
/// the CodedInputStream's state needs to be updated.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void Initialize(CodedInputStream input, out ParseContext ctx)
{
ctx.buffer = new ReadOnlySpan<byte>(input.InternalBuffer);
// ideally we would use a reference to the original state, but that doesn't seem possible
// so we just copy the struct that holds the state. We will need to later store the state back
// into CodedInputStream if we want to keep it usable.
ctx.state = input.InternalState;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void Initialize(ReadOnlySequence<byte> input, out ParseContext ctx)
{
Initialize(input, DefaultRecursionLimit, out ctx);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void Initialize(ReadOnlySequence<byte> input, int recursionLimit, out ParseContext ctx)
{
ctx.buffer = default;
ctx.state = default;
ctx.state.lastTag = 0;
ctx.state.recursionDepth = 0;
ctx.state.sizeLimit = DefaultSizeLimit;
ctx.state.recursionLimit = recursionLimit;
ctx.state.currentLimit = int.MaxValue;
SegmentedBufferHelper.Initialize(input, out ctx.state.segmentedBufferHelper, out ctx.buffer);
ctx.state.bufferPos = 0;
ctx.state.bufferSize = ctx.buffer.Length;
ctx.state.DiscardUnknownFields = false;
ctx.state.ExtensionRegistry = null;
}
/// <summary>
/// Returns the last tag read, or 0 if no tags have been read or we've read beyond
/// the end of the input.
/// </summary>
internal uint LastTag { get { return state.lastTag; } }
/// <summary>
/// Internal-only property; when set to true, unknown fields will be discarded while parsing.
/// </summary>
internal bool DiscardUnknownFields {
get { return state.DiscardUnknownFields; }
set { state.DiscardUnknownFields = value; }
}
/// <summary>
/// Internal-only property; provides extension identifiers to compatible messages while parsing.
/// </summary>
internal ExtensionRegistry ExtensionRegistry
{
get { return state.ExtensionRegistry; }
set { state.ExtensionRegistry = value; }
}
/// <summary>
/// Reads a field tag, returning the tag of 0 for "end of input".
/// </summary>
/// <remarks>
/// If this method returns 0, it doesn't necessarily mean the end of all
/// the data in this CodedInputReader; it may be the end of the logical input
/// for an embedded message, for example.
/// </remarks>
/// <returns>The next field tag, or 0 for end of input. (0 is never a valid tag.)</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint ReadTag()
{
return ParsingPrimitives.ParseTag(ref buffer, ref state);
}
/// <summary>
/// Reads a double field from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double ReadDouble()
{
return ParsingPrimitives.ParseDouble(ref buffer, ref state);
}
/// <summary>
/// Reads a float field from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float ReadFloat()
{
return ParsingPrimitives.ParseFloat(ref buffer, ref state);
}
/// <summary>
/// Reads a uint64 field from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong ReadUInt64()
{
return ParsingPrimitives.ParseRawVarint64(ref buffer, ref state);
}
/// <summary>
/// Reads an int64 field from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public long ReadInt64()
{
return (long)ParsingPrimitives.ParseRawVarint64(ref buffer, ref state);
}
/// <summary>
/// Reads an int32 field from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadInt32()
{
return (int)ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
}
/// <summary>
/// Reads a fixed64 field from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong ReadFixed64()
{
return ParsingPrimitives.ParseRawLittleEndian64(ref buffer, ref state);
}
/// <summary>
/// Reads a fixed32 field from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint ReadFixed32()
{
return ParsingPrimitives.ParseRawLittleEndian32(ref buffer, ref state);
}
/// <summary>
/// Reads a bool field from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool ReadBool()
{
return ParsingPrimitives.ParseRawVarint64(ref buffer, ref state) != 0;
}
/// <summary>
/// Reads a string field from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadString()
{
return ParsingPrimitives.ReadString(ref buffer, ref state);
}
/// <summary>
/// Reads an embedded message field value from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadMessage(IMessage message)
{
ParsingPrimitivesMessages.ReadMessage(ref this, message);
}
/// <summary>
/// Reads an embedded group field from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadGroup(IMessage message)
{
ParsingPrimitivesMessages.ReadGroup(ref this, message);
}
/// <summary>
/// Reads a bytes field value from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ByteString ReadBytes()
{
return ParsingPrimitives.ReadBytes(ref buffer, ref state);
}
/// <summary>
/// Reads a uint32 field value from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint ReadUInt32()
{
return ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
}
/// <summary>
/// Reads an enum field value from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadEnum()
{
// Currently just a pass-through, but it's nice to separate it logically from WriteInt32.
return (int)ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
}
/// <summary>
/// Reads an sfixed32 field value from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadSFixed32()
{
return (int)ParsingPrimitives.ParseRawLittleEndian32(ref buffer, ref state);
}
/// <summary>
/// Reads an sfixed64 field value from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public long ReadSFixed64()
{
return (long)ParsingPrimitives.ParseRawLittleEndian64(ref buffer, ref state);
}
/// <summary>
/// Reads an sint32 field value from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadSInt32()
{
return ParsingPrimitives.DecodeZigZag32(ParsingPrimitives.ParseRawVarint32(ref buffer, ref state));
}
/// <summary>
/// Reads an sint64 field value from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public long ReadSInt64()
{
return ParsingPrimitives.DecodeZigZag64(ParsingPrimitives.ParseRawVarint64(ref buffer, ref state));
}
/// <summary>
/// Reads a length for length-delimited data.
/// </summary>
/// <remarks>
/// This is internally just reading a varint, but this method exists
/// to make the calling code clearer.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadLength()
{
return (int)ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
}
internal void CopyStateTo(CodedInputStream input)
{
input.InternalState = state;
}
internal void LoadStateFrom(CodedInputStream input)
{
state = input.InternalState;
}
}
}

View File

@ -0,0 +1,115 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using LC.Google.Protobuf.Collections;
namespace LC.Google.Protobuf
{
// warning: this is a mutable struct, so it needs to be only passed as a ref!
internal struct ParserInternalState
{
// NOTE: the Span representing the current buffer is kept separate so that this doesn't have to be a ref struct and so it can
// be included in CodedInputStream's internal state
/// <summary>
/// The position within the current buffer (i.e. the next byte to read)
/// </summary>
internal int bufferPos;
/// <summary>
/// Size of the current buffer
/// </summary>
internal int bufferSize;
/// <summary>
/// If we are currently inside a length-delimited block, this is the number of
/// bytes in the buffer that are still available once we leave the delimited block.
/// </summary>
internal int bufferSizeAfterLimit;
/// <summary>
/// The absolute position of the end of the current length-delimited block (including totalBytesRetired)
/// </summary>
internal int currentLimit;
/// <summary>
/// The total number of consumed before the start of the current buffer. The
/// total bytes read up to the current position can be computed as
/// totalBytesRetired + bufferPos.
/// </summary>
internal int totalBytesRetired;
internal int recursionDepth; // current recursion depth
internal SegmentedBufferHelper segmentedBufferHelper;
/// <summary>
/// The last tag we read. 0 indicates we've read to the end of the stream
/// (or haven't read anything yet).
/// </summary>
internal uint lastTag;
/// <summary>
/// The next tag, used to store the value read by PeekTag.
/// </summary>
internal uint nextTag;
internal bool hasNextTag;
// these fields are configuration, they should be readonly
internal int sizeLimit;
internal int recursionLimit;
// If non-null, the top level parse method was started with given coded input stream as an argument
// which also means we can potentially fallback to calling MergeFrom(CodedInputStream cis) if needed.
internal CodedInputStream CodedInputStream => segmentedBufferHelper.CodedInputStream;
/// <summary>
/// Internal-only property; when set to true, unknown fields will be discarded while parsing.
/// </summary>
internal bool DiscardUnknownFields { get; set; }
/// <summary>
/// Internal-only property; provides extension identifiers to compatible messages while parsing.
/// </summary>
internal ExtensionRegistry ExtensionRegistry { get; set; }
}
}

View File

@ -0,0 +1,815 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using LC.Google.Protobuf.Collections;
namespace LC.Google.Protobuf
{
/// <summary>
/// Primitives for parsing protobuf wire format.
/// </summary>
[SecuritySafeCritical]
internal static class ParsingPrimitives
{
private const int StackallocThreshold = 256;
/// <summary>
/// Reads a length for length-delimited data.
/// </summary>
/// <remarks>
/// This is internally just reading a varint, but this method exists
/// to make the calling code clearer.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ParseLength(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
return (int)ParseRawVarint32(ref buffer, ref state);
}
/// <summary>
/// Parses the next tag.
/// If the end of logical stream was reached, an invalid tag of 0 is returned.
/// </summary>
public static uint ParseTag(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
// The "nextTag" logic is there only as an optimization for reading non-packed repeated / map
// fields and is strictly speaking not necessary.
// TODO(jtattermusch): look into simplifying the ParseTag logic.
if (state.hasNextTag)
{
state.lastTag = state.nextTag;
state.hasNextTag = false;
return state.lastTag;
}
// Optimize for the incredibly common case of having at least two bytes left in the buffer,
// and those two bytes being enough to get the tag. This will be true for fields up to 4095.
if (state.bufferPos + 2 <= state.bufferSize)
{
int tmp = buffer[state.bufferPos++];
if (tmp < 128)
{
state.lastTag = (uint)tmp;
}
else
{
int result = tmp & 0x7f;
if ((tmp = buffer[state.bufferPos++]) < 128)
{
result |= tmp << 7;
state.lastTag = (uint) result;
}
else
{
// Nope, rewind and go the potentially slow route.
state.bufferPos -= 2;
state.lastTag = ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
}
}
}
else
{
if (SegmentedBufferHelper.IsAtEnd(ref buffer, ref state))
{
state.lastTag = 0;
return 0;
}
state.lastTag = ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
}
if (WireFormat.GetTagFieldNumber(state.lastTag) == 0)
{
// If we actually read a tag with a field of 0, that's not a valid tag.
throw InvalidProtocolBufferException.InvalidTag();
}
return state.lastTag;
}
/// <summary>
/// Peeks at the next tag in the stream. If it matches <paramref name="tag"/>,
/// the tag is consumed and the method returns <c>true</c>; otherwise, the
/// stream is left in the original position and the method returns <c>false</c>.
/// </summary>
public static bool MaybeConsumeTag(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, uint tag)
{
if (PeekTag(ref buffer, ref state) == tag)
{
state.hasNextTag = false;
return true;
}
return false;
}
/// <summary>
/// Peeks at the next field tag. This is like calling <see cref="ParseTag"/>, but the
/// tag is not consumed. (So a subsequent call to <see cref="ParseTag"/> will return the
/// same value.)
/// </summary>
public static uint PeekTag(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
if (state.hasNextTag)
{
return state.nextTag;
}
uint savedLast = state.lastTag;
state.nextTag = ParseTag(ref buffer, ref state);
state.hasNextTag = true;
state.lastTag = savedLast; // Undo the side effect of ReadTag
return state.nextTag;
}
/// <summary>
/// Parses a raw varint.
/// </summary>
public static ulong ParseRawVarint64(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
if (state.bufferPos + 10 > state.bufferSize)
{
return ParseRawVarint64SlowPath(ref buffer, ref state);
}
ulong result = buffer[state.bufferPos++];
if (result < 128)
{
return result;
}
result &= 0x7f;
int shift = 7;
do
{
byte b = buffer[state.bufferPos++];
result |= (ulong)(b & 0x7F) << shift;
if (b < 0x80)
{
return result;
}
shift += 7;
}
while (shift < 64);
throw InvalidProtocolBufferException.MalformedVarint();
}
private static ulong ParseRawVarint64SlowPath(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
int shift = 0;
ulong result = 0;
do
{
byte b = ReadRawByte(ref buffer, ref state);
result |= (ulong)(b & 0x7F) << shift;
if (b < 0x80)
{
return result;
}
shift += 7;
}
while (shift < 64);
throw InvalidProtocolBufferException.MalformedVarint();
}
/// <summary>
/// Parses a raw Varint. If larger than 32 bits, discard the upper bits.
/// This method is optimised for the case where we've got lots of data in the buffer.
/// That means we can check the size just once, then just read directly from the buffer
/// without constant rechecking of the buffer length.
/// </summary>
public static uint ParseRawVarint32(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
if (state.bufferPos + 5 > state.bufferSize)
{
return ParseRawVarint32SlowPath(ref buffer, ref state);
}
int tmp = buffer[state.bufferPos++];
if (tmp < 128)
{
return (uint)tmp;
}
int result = tmp & 0x7f;
if ((tmp = buffer[state.bufferPos++]) < 128)
{
result |= tmp << 7;
}
else
{
result |= (tmp & 0x7f) << 7;
if ((tmp = buffer[state.bufferPos++]) < 128)
{
result |= tmp << 14;
}
else
{
result |= (tmp & 0x7f) << 14;
if ((tmp = buffer[state.bufferPos++]) < 128)
{
result |= tmp << 21;
}
else
{
result |= (tmp & 0x7f) << 21;
result |= (tmp = buffer[state.bufferPos++]) << 28;
if (tmp >= 128)
{
// Discard upper 32 bits.
// Note that this has to use ReadRawByte() as we only ensure we've
// got at least 5 bytes at the start of the method. This lets us
// use the fast path in more cases, and we rarely hit this section of code.
for (int i = 0; i < 5; i++)
{
if (ReadRawByte(ref buffer, ref state) < 128)
{
return (uint) result;
}
}
throw InvalidProtocolBufferException.MalformedVarint();
}
}
}
}
return (uint)result;
}
private static uint ParseRawVarint32SlowPath(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
int tmp = ReadRawByte(ref buffer, ref state);
if (tmp < 128)
{
return (uint) tmp;
}
int result = tmp & 0x7f;
if ((tmp = ReadRawByte(ref buffer, ref state)) < 128)
{
result |= tmp << 7;
}
else
{
result |= (tmp & 0x7f) << 7;
if ((tmp = ReadRawByte(ref buffer, ref state)) < 128)
{
result |= tmp << 14;
}
else
{
result |= (tmp & 0x7f) << 14;
if ((tmp = ReadRawByte(ref buffer, ref state)) < 128)
{
result |= tmp << 21;
}
else
{
result |= (tmp & 0x7f) << 21;
result |= (tmp = ReadRawByte(ref buffer, ref state)) << 28;
if (tmp >= 128)
{
// Discard upper 32 bits.
for (int i = 0; i < 5; i++)
{
if (ReadRawByte(ref buffer, ref state) < 128)
{
return (uint) result;
}
}
throw InvalidProtocolBufferException.MalformedVarint();
}
}
}
}
return (uint) result;
}
/// <summary>
/// Parses a 32-bit little-endian integer.
/// </summary>
public static uint ParseRawLittleEndian32(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
const int uintLength = sizeof(uint);
const int ulongLength = sizeof(ulong);
if (state.bufferPos + ulongLength > state.bufferSize)
{
return ParseRawLittleEndian32SlowPath(ref buffer, ref state);
}
// ReadUInt32LittleEndian is many times slower than ReadUInt64LittleEndian (at least on some runtimes)
// so it's faster better to use ReadUInt64LittleEndian and truncate the result.
uint result = (uint) BinaryPrimitives.ReadUInt64LittleEndian(buffer.Slice(state.bufferPos, ulongLength));
state.bufferPos += uintLength;
return result;
}
private static uint ParseRawLittleEndian32SlowPath(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
uint b1 = ReadRawByte(ref buffer, ref state);
uint b2 = ReadRawByte(ref buffer, ref state);
uint b3 = ReadRawByte(ref buffer, ref state);
uint b4 = ReadRawByte(ref buffer, ref state);
return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24);
}
/// <summary>
/// Parses a 64-bit little-endian integer.
/// </summary>
public static ulong ParseRawLittleEndian64(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
const int length = sizeof(ulong);
if (state.bufferPos + length > state.bufferSize)
{
return ParseRawLittleEndian64SlowPath(ref buffer, ref state);
}
ulong result = BinaryPrimitives.ReadUInt64LittleEndian(buffer.Slice(state.bufferPos, length));
state.bufferPos += length;
return result;
}
private static ulong ParseRawLittleEndian64SlowPath(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
ulong b1 = ReadRawByte(ref buffer, ref state);
ulong b2 = ReadRawByte(ref buffer, ref state);
ulong b3 = ReadRawByte(ref buffer, ref state);
ulong b4 = ReadRawByte(ref buffer, ref state);
ulong b5 = ReadRawByte(ref buffer, ref state);
ulong b6 = ReadRawByte(ref buffer, ref state);
ulong b7 = ReadRawByte(ref buffer, ref state);
ulong b8 = ReadRawByte(ref buffer, ref state);
return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24)
| (b5 << 32) | (b6 << 40) | (b7 << 48) | (b8 << 56);
}
/// <summary>
/// Parses a double value.
/// </summary>
public static double ParseDouble(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
const int length = sizeof(double);
if (!BitConverter.IsLittleEndian || state.bufferPos + length > state.bufferSize)
{
return BitConverter.Int64BitsToDouble((long)ParseRawLittleEndian64(ref buffer, ref state));
}
// ReadUnaligned uses processor architecture for endianness.
double result = Unsafe.ReadUnaligned<double>(ref MemoryMarshal.GetReference(buffer.Slice(state.bufferPos, length)));
state.bufferPos += length;
return result;
}
/// <summary>
/// Parses a float value.
/// </summary>
public static float ParseFloat(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
const int length = sizeof(float);
if (!BitConverter.IsLittleEndian || state.bufferPos + length > state.bufferSize)
{
return ParseFloatSlow(ref buffer, ref state);
}
// ReadUnaligned uses processor architecture for endianness.
float result = Unsafe.ReadUnaligned<float>(ref MemoryMarshal.GetReference(buffer.Slice(state.bufferPos, length)));
state.bufferPos += length;
return result;
}
private static unsafe float ParseFloatSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
const int length = sizeof(float);
byte* stackBuffer = stackalloc byte[length];
Span<byte> tempSpan = new Span<byte>(stackBuffer, length);
for (int i = 0; i < length; i++)
{
tempSpan[i] = ReadRawByte(ref buffer, ref state);
}
// Content is little endian. Reverse if needed to match endianness of architecture.
if (!BitConverter.IsLittleEndian)
{
tempSpan.Reverse();
}
return Unsafe.ReadUnaligned<float>(ref MemoryMarshal.GetReference(tempSpan));
}
/// <summary>
/// Reads a fixed size of bytes from the input.
/// </summary>
/// <exception cref="InvalidProtocolBufferException">
/// the end of the stream or the current limit was reached
/// </exception>
public static byte[] ReadRawBytes(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int size)
{
if (size < 0)
{
throw InvalidProtocolBufferException.NegativeSize();
}
if (size <= state.bufferSize - state.bufferPos)
{
// We have all the bytes we need already.
byte[] bytes = new byte[size];
buffer.Slice(state.bufferPos, size).CopyTo(bytes);
state.bufferPos += size;
return bytes;
}
return ReadRawBytesSlow(ref buffer, ref state, size);
}
private static byte[] ReadRawBytesSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int size)
{
ValidateCurrentLimit(ref buffer, ref state, size);
if ((!state.segmentedBufferHelper.TotalLength.HasValue && size < buffer.Length) ||
IsDataAvailableInSource(ref state, size))
{
// Reading more bytes than are in the buffer, but not an excessive number
// of bytes. We can safely allocate the resulting array ahead of time.
byte[] bytes = new byte[size];
ReadRawBytesIntoSpan(ref buffer, ref state, size, bytes);
return bytes;
}
else
{
// The size is very large. For security reasons, we can't allocate the
// entire byte array yet. The size comes directly from the input, so a
// maliciously-crafted message could provide a bogus very large size in
// order to trick the app into allocating a lot of memory. We avoid this
// by allocating and reading only a small chunk at a time, so that the
// malicious message must actually *be* extremely large to cause
// problems. Meanwhile, we limit the allowed size of a message elsewhere.
List<byte[]> chunks = new List<byte[]>();
int pos = state.bufferSize - state.bufferPos;
byte[] firstChunk = new byte[pos];
buffer.Slice(state.bufferPos, pos).CopyTo(firstChunk);
chunks.Add(firstChunk);
state.bufferPos = state.bufferSize;
// Read all the rest of the bytes we need.
int sizeLeft = size - pos;
while (sizeLeft > 0)
{
state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true);
byte[] chunk = new byte[Math.Min(sizeLeft, state.bufferSize)];
buffer.Slice(0, chunk.Length)
.CopyTo(chunk);
state.bufferPos += chunk.Length;
sizeLeft -= chunk.Length;
chunks.Add(chunk);
}
// OK, got everything. Now concatenate it all into one buffer.
byte[] bytes = new byte[size];
int newPos = 0;
foreach (byte[] chunk in chunks)
{
Buffer.BlockCopy(chunk, 0, bytes, newPos, chunk.Length);
newPos += chunk.Length;
}
// Done.
return bytes;
}
}
/// <summary>
/// Reads and discards <paramref name="size"/> bytes.
/// </summary>
/// <exception cref="InvalidProtocolBufferException">the end of the stream
/// or the current limit was reached</exception>
public static void SkipRawBytes(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int size)
{
if (size < 0)
{
throw InvalidProtocolBufferException.NegativeSize();
}
ValidateCurrentLimit(ref buffer, ref state, size);
if (size <= state.bufferSize - state.bufferPos)
{
// We have all the bytes we need already.
state.bufferPos += size;
}
else
{
// Skipping more bytes than are in the buffer. First skip what we have.
int pos = state.bufferSize - state.bufferPos;
state.bufferPos = state.bufferSize;
// TODO: If our segmented buffer is backed by a Stream that is seekable, we could skip the bytes more efficiently
// by simply updating stream's Position property. This used to be supported in the past, but the support was dropped
// because it would make the segmentedBufferHelper more complex. Support can be reintroduced if needed.
state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true);
while (size - pos > state.bufferSize)
{
pos += state.bufferSize;
state.bufferPos = state.bufferSize;
state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true);
}
state.bufferPos = size - pos;
}
}
/// <summary>
/// Reads a string field value from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string ReadString(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
int length = ParsingPrimitives.ParseLength(ref buffer, ref state);
return ParsingPrimitives.ReadRawString(ref buffer, ref state, length);
}
/// <summary>
/// Reads a bytes field value from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ByteString ReadBytes(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
int length = ParsingPrimitives.ParseLength(ref buffer, ref state);
return ByteString.AttachBytes(ParsingPrimitives.ReadRawBytes(ref buffer, ref state, length));
}
/// <summary>
/// Reads a UTF-8 string from the next "length" bytes.
/// </summary>
/// <exception cref="InvalidProtocolBufferException">
/// the end of the stream or the current limit was reached
/// </exception>
[SecuritySafeCritical]
public static string ReadRawString(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int length)
{
// No need to read any data for an empty string.
if (length == 0)
{
return string.Empty;
}
if (length < 0)
{
throw InvalidProtocolBufferException.NegativeSize();
}
#if GOOGLE_PROTOBUF_SUPPORT_FAST_STRING
if (length <= state.bufferSize - state.bufferPos)
{
// Fast path: all bytes to decode appear in the same span.
ReadOnlySpan<byte> data = buffer.Slice(state.bufferPos, length);
string value;
unsafe
{
fixed (byte* sourceBytes = &MemoryMarshal.GetReference(data))
{
value = WritingPrimitives.Utf8Encoding.GetString(sourceBytes, length);
}
}
state.bufferPos += length;
return value;
}
#endif
return ReadStringSlow(ref buffer, ref state, length);
}
/// <summary>
/// Reads a string assuming that it is spread across multiple spans in a <see cref="ReadOnlySequence{T}"/>.
/// </summary>
private static string ReadStringSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int length)
{
ValidateCurrentLimit(ref buffer, ref state, length);
#if GOOGLE_PROTOBUF_SUPPORT_FAST_STRING
if (IsDataAvailable(ref state, length))
{
// Read string data into a temporary buffer, either stackalloc'ed or from ArrayPool
// Once all data is read then call Encoding.GetString on buffer and return to pool if needed.
byte[] byteArray = null;
Span<byte> byteSpan = length <= StackallocThreshold ?
stackalloc byte[length] :
(byteArray = ArrayPool<byte>.Shared.Rent(length));
try
{
unsafe
{
fixed (byte* pByteSpan = &MemoryMarshal.GetReference(byteSpan))
{
// Compiler doesn't like that a potentially stackalloc'd Span<byte> is being used
// in a method with a "ref Span<byte> buffer" argument. If the stackalloc'd span was assigned
// to the ref argument then bad things would happen. We'll never do that so it is ok.
// Make compiler happy by passing a new span created from pointer.
var tempSpan = new Span<byte>(pByteSpan, byteSpan.Length);
ReadRawBytesIntoSpan(ref buffer, ref state, length, tempSpan);
return WritingPrimitives.Utf8Encoding.GetString(pByteSpan, length);
}
}
}
finally
{
if (byteArray != null)
{
ArrayPool<byte>.Shared.Return(byteArray);
}
}
}
#endif
// Slow path: Build a byte array first then copy it.
// This will be called when reading from a Stream because we don't know the length of the stream,
// or there is not enough data in the sequence. If there is not enough data then ReadRawBytes will
// throw an exception.
return WritingPrimitives.Utf8Encoding.GetString(ReadRawBytes(ref buffer, ref state, length), 0, length);
}
/// <summary>
/// Validates that the specified size doesn't exceed the current limit. If it does then remaining bytes
/// are skipped and an error is thrown.
/// </summary>
private static void ValidateCurrentLimit(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int size)
{
if (state.totalBytesRetired + state.bufferPos + size > state.currentLimit)
{
// Read to the end of the stream (up to the current limit) anyway.
SkipRawBytes(ref buffer, ref state, state.currentLimit - state.totalBytesRetired - state.bufferPos);
// Then fail.
throw InvalidProtocolBufferException.TruncatedMessage();
}
}
[SecuritySafeCritical]
private static byte ReadRawByte(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
if (state.bufferPos == state.bufferSize)
{
state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true);
}
return buffer[state.bufferPos++];
}
/// <summary>
/// Reads a varint from the input one byte at a time, so that it does not
/// read any bytes after the end of the varint. If you simply wrapped the
/// stream in a CodedInputStream and used ReadRawVarint32(Stream)
/// then you would probably end up reading past the end of the varint since
/// CodedInputStream buffers its input.
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static uint ReadRawVarint32(Stream input)
{
int result = 0;
int offset = 0;
for (; offset < 32; offset += 7)
{
int b = input.ReadByte();
if (b == -1)
{
throw InvalidProtocolBufferException.TruncatedMessage();
}
result |= (b & 0x7f) << offset;
if ((b & 0x80) == 0)
{
return (uint) result;
}
}
// Keep reading up to 64 bits.
for (; offset < 64; offset += 7)
{
int b = input.ReadByte();
if (b == -1)
{
throw InvalidProtocolBufferException.TruncatedMessage();
}
if ((b & 0x80) == 0)
{
return (uint) result;
}
}
throw InvalidProtocolBufferException.MalformedVarint();
}
/// <summary>
/// Decode a 32-bit value with ZigZag encoding.
/// </summary>
/// <remarks>
/// ZigZag encodes signed integers into values that can be efficiently
/// encoded with varint. (Otherwise, negative values must be
/// sign-extended to 32 bits to be varint encoded, thus always taking
/// 5 bytes on the wire.)
/// </remarks>
public static int DecodeZigZag32(uint n)
{
return (int)(n >> 1) ^ -(int)(n & 1);
}
/// <summary>
/// Decode a 64-bit value with ZigZag encoding.
/// </summary>
/// <remarks>
/// ZigZag encodes signed integers into values that can be efficiently
/// encoded with varint. (Otherwise, negative values must be
/// sign-extended to 64 bits to be varint encoded, thus always taking
/// 10 bytes on the wire.)
/// </remarks>
public static long DecodeZigZag64(ulong n)
{
return (long)(n >> 1) ^ -(long)(n & 1);
}
/// <summary>
/// Checks whether there is known data available of the specified size remaining to parse.
/// When parsing from a Stream this can return false because we have no knowledge of the amount
/// of data remaining in the stream until it is read.
/// </summary>
public static bool IsDataAvailable(ref ParserInternalState state, int size)
{
// Data fits in remaining buffer
if (size <= state.bufferSize - state.bufferPos)
{
return true;
}
return IsDataAvailableInSource(ref state, size);
}
/// <summary>
/// Checks whether there is known data available of the specified size remaining to parse
/// in the underlying data source.
/// When parsing from a Stream this will return false because we have no knowledge of the amount
/// of data remaining in the stream until it is read.
/// </summary>
private static bool IsDataAvailableInSource(ref ParserInternalState state, int size)
{
// Data fits in remaining source data.
// Note that this will never be true when reading from a stream as the total length is unknown.
return size <= state.segmentedBufferHelper.TotalLength - state.totalBytesRetired - state.bufferPos;
}
/// <summary>
/// Read raw bytes of the specified length into a span. The amount of data available and the current limit should
/// be checked before calling this method.
/// </summary>
private static void ReadRawBytesIntoSpan(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int length, Span<byte> byteSpan)
{
int remainingByteLength = length;
while (remainingByteLength > 0)
{
if (state.bufferSize - state.bufferPos == 0)
{
state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true);
}
ReadOnlySpan<byte> unreadSpan = buffer.Slice(state.bufferPos, Math.Min(remainingByteLength, state.bufferSize - state.bufferPos));
unreadSpan.CopyTo(byteSpan.Slice(length - remainingByteLength));
remainingByteLength -= unreadSpan.Length;
state.bufferPos += unreadSpan.Length;
}
}
}
}

View File

@ -0,0 +1,229 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using System.Security;
namespace LC.Google.Protobuf
{
/// <summary>
/// Reading and skipping messages / groups
/// </summary>
[SecuritySafeCritical]
internal static class ParsingPrimitivesMessages
{
public static void SkipLastField(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
if (state.lastTag == 0)
{
throw new InvalidOperationException("SkipLastField cannot be called at the end of a stream");
}
switch (WireFormat.GetTagWireType(state.lastTag))
{
case WireFormat.WireType.StartGroup:
SkipGroup(ref buffer, ref state, state.lastTag);
break;
case WireFormat.WireType.EndGroup:
throw new InvalidProtocolBufferException(
"SkipLastField called on an end-group tag, indicating that the corresponding start-group was missing");
case WireFormat.WireType.Fixed32:
ParsingPrimitives.ParseRawLittleEndian32(ref buffer, ref state);
break;
case WireFormat.WireType.Fixed64:
ParsingPrimitives.ParseRawLittleEndian64(ref buffer, ref state);
break;
case WireFormat.WireType.LengthDelimited:
var length = ParsingPrimitives.ParseLength(ref buffer, ref state);
ParsingPrimitives.SkipRawBytes(ref buffer, ref state, length);
break;
case WireFormat.WireType.Varint:
ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
break;
}
}
/// <summary>
/// Skip a group.
/// </summary>
public static void SkipGroup(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, uint startGroupTag)
{
// Note: Currently we expect this to be the way that groups are read. We could put the recursion
// depth changes into the ReadTag method instead, potentially...
state.recursionDepth++;
if (state.recursionDepth >= state.recursionLimit)
{
throw InvalidProtocolBufferException.RecursionLimitExceeded();
}
uint tag;
while (true)
{
tag = ParsingPrimitives.ParseTag(ref buffer, ref state);
if (tag == 0)
{
throw InvalidProtocolBufferException.TruncatedMessage();
}
// Can't call SkipLastField for this case- that would throw.
if (WireFormat.GetTagWireType(tag) == WireFormat.WireType.EndGroup)
{
break;
}
// This recursion will allow us to handle nested groups.
SkipLastField(ref buffer, ref state);
}
int startField = WireFormat.GetTagFieldNumber(startGroupTag);
int endField = WireFormat.GetTagFieldNumber(tag);
if (startField != endField)
{
throw new InvalidProtocolBufferException(
$"Mismatched end-group tag. Started with field {startField}; ended with field {endField}");
}
state.recursionDepth--;
}
public static void ReadMessage(ref ParseContext ctx, IMessage message)
{
int length = ParsingPrimitives.ParseLength(ref ctx.buffer, ref ctx.state);
if (ctx.state.recursionDepth >= ctx.state.recursionLimit)
{
throw InvalidProtocolBufferException.RecursionLimitExceeded();
}
int oldLimit = SegmentedBufferHelper.PushLimit(ref ctx.state, length);
++ctx.state.recursionDepth;
ReadRawMessage(ref ctx, message);
CheckReadEndOfStreamTag(ref ctx.state);
// Check that we've read exactly as much data as expected.
if (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state))
{
throw InvalidProtocolBufferException.TruncatedMessage();
}
--ctx.state.recursionDepth;
SegmentedBufferHelper.PopLimit(ref ctx.state, oldLimit);
}
public static void ReadGroup(ref ParseContext ctx, IMessage message)
{
if (ctx.state.recursionDepth >= ctx.state.recursionLimit)
{
throw InvalidProtocolBufferException.RecursionLimitExceeded();
}
++ctx.state.recursionDepth;
uint tag = ctx.state.lastTag;
int fieldNumber = WireFormat.GetTagFieldNumber(tag);
ReadRawMessage(ref ctx, message);
CheckLastTagWas(ref ctx.state, WireFormat.MakeTag(fieldNumber, WireFormat.WireType.EndGroup));
--ctx.state.recursionDepth;
}
public static void ReadGroup(ref ParseContext ctx, int fieldNumber, UnknownFieldSet set)
{
if (ctx.state.recursionDepth >= ctx.state.recursionLimit)
{
throw InvalidProtocolBufferException.RecursionLimitExceeded();
}
++ctx.state.recursionDepth;
set.MergeGroupFrom(ref ctx);
CheckLastTagWas(ref ctx.state, WireFormat.MakeTag(fieldNumber, WireFormat.WireType.EndGroup));
--ctx.state.recursionDepth;
}
public static void ReadRawMessage(ref ParseContext ctx, IMessage message)
{
if (message is IBufferMessage bufferMessage)
{
bufferMessage.InternalMergeFrom(ref ctx);
}
else
{
// If we reached here, it means we've ran into a nested message with older generated code
// which doesn't provide the InternalMergeFrom method that takes a ParseContext.
// With a slight performance overhead, we can still parse this message just fine,
// but we need to find the original CodedInputStream instance that initiated this
// parsing process and make sure its internal state is up to date.
// Note that this performance overhead is not very high (basically copying contents of a struct)
// and it will only be incurred in case the application mixes older and newer generated code.
// Regenerating the code from .proto files will remove this overhead because it will
// generate the InternalMergeFrom method we need.
if (ctx.state.CodedInputStream == null)
{
// This can only happen when the parsing started without providing a CodedInputStream instance
// (e.g. ParseContext was created directly from a ReadOnlySequence).
// That also means that one of the new parsing APIs was used at the top level
// and in such case it is reasonable to require that all the nested message provide
// up-to-date generated code with ParseContext support (and fail otherwise).
throw new InvalidProtocolBufferException($"Message {message.GetType().Name} doesn't provide the generated method that enables ParseContext-based parsing. You might need to regenerate the generated protobuf code.");
}
ctx.CopyStateTo(ctx.state.CodedInputStream);
try
{
// fallback parse using the CodedInputStream that started current parsing tree
message.MergeFrom(ctx.state.CodedInputStream);
}
finally
{
ctx.LoadStateFrom(ctx.state.CodedInputStream);
}
}
}
/// <summary>
/// Verifies that the last call to ReadTag() returned tag 0 - in other words,
/// we've reached the end of the stream when we expected to.
/// </summary>
/// <exception cref="InvalidProtocolBufferException">The
/// tag read was not the one specified</exception>
public static void CheckReadEndOfStreamTag(ref ParserInternalState state)
{
if (state.lastTag != 0)
{
throw InvalidProtocolBufferException.MoreDataAvailable();
}
}
private static void CheckLastTagWas(ref ParserInternalState state, uint expectedTag)
{
if (state.lastTag != expectedTag) {
throw InvalidProtocolBufferException.InvalidEndTag();
}
}
}
}

View File

@ -0,0 +1,355 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using LC.Google.Protobuf.Collections;
namespace LC.Google.Protobuf
{
/// <summary>
/// Fast parsing primitives for wrapper types
/// </summary>
[SecuritySafeCritical]
internal static class ParsingPrimitivesWrappers
{
internal static float? ReadFloatWrapperLittleEndian(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
// length:1 + tag:1 + value:4 = 6 bytes
if (state.bufferPos + 6 <= state.bufferSize)
{
// The entire wrapper message is already contained in `buffer`.
int length = buffer[state.bufferPos];
if (length == 0)
{
state.bufferPos++;
return 0F;
}
// tag:1 + value:4 = length of 5 bytes
// field=1, type=32-bit = tag of 13
if (length != 5 || buffer[state.bufferPos + 1] != 13)
{
return ReadFloatWrapperSlow(ref buffer, ref state);
}
state.bufferPos += 2;
return ParsingPrimitives.ParseFloat(ref buffer, ref state);
}
else
{
return ReadFloatWrapperSlow(ref buffer, ref state);
}
}
internal static float? ReadFloatWrapperSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
int length = ParsingPrimitives.ParseLength(ref buffer, ref state);
if (length == 0)
{
return 0F;
}
int finalBufferPos = state.totalBytesRetired + state.bufferPos + length;
float result = 0F;
do
{
// field=1, type=32-bit = tag of 13
if (ParsingPrimitives.ParseTag(ref buffer, ref state) == 13)
{
result = ParsingPrimitives.ParseFloat(ref buffer, ref state);
}
else
{
ParsingPrimitivesMessages.SkipLastField(ref buffer, ref state);
}
}
while (state.totalBytesRetired + state.bufferPos < finalBufferPos);
return result;
}
internal static double? ReadDoubleWrapperLittleEndian(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
// length:1 + tag:1 + value:8 = 10 bytes
if (state.bufferPos + 10 <= state.bufferSize)
{
// The entire wrapper message is already contained in `buffer`.
int length = buffer[state.bufferPos];
if (length == 0)
{
state.bufferPos++;
return 0D;
}
// tag:1 + value:8 = length of 9 bytes
// field=1, type=64-bit = tag of 9
if (length != 9 || buffer[state.bufferPos + 1] != 9)
{
return ReadDoubleWrapperSlow(ref buffer, ref state);
}
state.bufferPos += 2;
return ParsingPrimitives.ParseDouble(ref buffer, ref state);
}
else
{
return ReadDoubleWrapperSlow(ref buffer, ref state);
}
}
internal static double? ReadDoubleWrapperSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
int length = ParsingPrimitives.ParseLength(ref buffer, ref state);
if (length == 0)
{
return 0D;
}
int finalBufferPos = state.totalBytesRetired + state.bufferPos + length;
double result = 0D;
do
{
// field=1, type=64-bit = tag of 9
if (ParsingPrimitives.ParseTag(ref buffer, ref state) == 9)
{
result = ParsingPrimitives.ParseDouble(ref buffer, ref state);
}
else
{
ParsingPrimitivesMessages.SkipLastField(ref buffer, ref state);
}
}
while (state.totalBytesRetired + state.bufferPos < finalBufferPos);
return result;
}
internal static bool? ReadBoolWrapper(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
return ReadUInt64Wrapper(ref buffer, ref state) != 0;
}
internal static uint? ReadUInt32Wrapper(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
// field=1, type=varint = tag of 8
const int expectedTag = 8;
// length:1 + tag:1 + value:10(varint64-max) = 12 bytes
// Value can be 64 bits for negative integers
if (state.bufferPos + 12 <= state.bufferSize)
{
// The entire wrapper message is already contained in `buffer`.
int pos0 = state.bufferPos;
int length = buffer[state.bufferPos++];
if (length == 0)
{
return 0;
}
// Length will always fit in a single byte.
if (length >= 128)
{
state.bufferPos = pos0;
return ReadUInt32WrapperSlow(ref buffer, ref state);
}
int finalBufferPos = state.bufferPos + length;
if (buffer[state.bufferPos++] != expectedTag)
{
state.bufferPos = pos0;
return ReadUInt32WrapperSlow(ref buffer, ref state);
}
var result = ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
// Verify this message only contained a single field.
if (state.bufferPos != finalBufferPos)
{
state.bufferPos = pos0;
return ReadUInt32WrapperSlow(ref buffer, ref state);
}
return result;
}
else
{
return ReadUInt32WrapperSlow(ref buffer, ref state);
}
}
internal static uint? ReadUInt32WrapperSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
int length = ParsingPrimitives.ParseLength(ref buffer, ref state);
if (length == 0)
{
return 0;
}
int finalBufferPos = state.totalBytesRetired + state.bufferPos + length;
uint result = 0;
do
{
// field=1, type=varint = tag of 8
if (ParsingPrimitives.ParseTag(ref buffer, ref state) == 8)
{
result = ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
}
else
{
ParsingPrimitivesMessages.SkipLastField(ref buffer, ref state);
}
}
while (state.totalBytesRetired + state.bufferPos < finalBufferPos);
return result;
}
internal static int? ReadInt32Wrapper(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
return (int?)ReadUInt32Wrapper(ref buffer, ref state);
}
internal static ulong? ReadUInt64Wrapper(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
// field=1, type=varint = tag of 8
const int expectedTag = 8;
// length:1 + tag:1 + value:10(varint64-max) = 12 bytes
if (state.bufferPos + 12 <= state.bufferSize)
{
// The entire wrapper message is already contained in `buffer`.
int pos0 = state.bufferPos;
int length = buffer[state.bufferPos++];
if (length == 0)
{
return 0L;
}
// Length will always fit in a single byte.
if (length >= 128)
{
state.bufferPos = pos0;
return ReadUInt64WrapperSlow(ref buffer, ref state);
}
int finalBufferPos = state.bufferPos + length;
if (buffer[state.bufferPos++] != expectedTag)
{
state.bufferPos = pos0;
return ReadUInt64WrapperSlow(ref buffer, ref state);
}
var result = ParsingPrimitives.ParseRawVarint64(ref buffer, ref state);
// Verify this message only contained a single field.
if (state.bufferPos != finalBufferPos)
{
state.bufferPos = pos0;
return ReadUInt64WrapperSlow(ref buffer, ref state);
}
return result;
}
else
{
return ReadUInt64WrapperSlow(ref buffer, ref state);
}
}
internal static ulong? ReadUInt64WrapperSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
// field=1, type=varint = tag of 8
const int expectedTag = 8;
int length = ParsingPrimitives.ParseLength(ref buffer, ref state);
if (length == 0)
{
return 0L;
}
int finalBufferPos = state.totalBytesRetired + state.bufferPos + length;
ulong result = 0L;
do
{
if (ParsingPrimitives.ParseTag(ref buffer, ref state) == expectedTag)
{
result = ParsingPrimitives.ParseRawVarint64(ref buffer, ref state);
}
else
{
ParsingPrimitivesMessages.SkipLastField(ref buffer, ref state);
}
}
while (state.totalBytesRetired + state.bufferPos < finalBufferPos);
return result;
}
internal static long? ReadInt64Wrapper(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
return (long?)ReadUInt64Wrapper(ref buffer, ref state);
}
internal static float? ReadFloatWrapperLittleEndian(ref ParseContext ctx)
{
return ParsingPrimitivesWrappers.ReadFloatWrapperLittleEndian(ref ctx.buffer, ref ctx.state);
}
internal static float? ReadFloatWrapperSlow(ref ParseContext ctx)
{
return ParsingPrimitivesWrappers.ReadFloatWrapperSlow(ref ctx.buffer, ref ctx.state);
}
internal static double? ReadDoubleWrapperLittleEndian(ref ParseContext ctx)
{
return ParsingPrimitivesWrappers.ReadDoubleWrapperLittleEndian(ref ctx.buffer, ref ctx.state);
}
internal static double? ReadDoubleWrapperSlow(ref ParseContext ctx)
{
return ParsingPrimitivesWrappers.ReadDoubleWrapperSlow(ref ctx.buffer, ref ctx.state);
}
internal static bool? ReadBoolWrapper(ref ParseContext ctx)
{
return ParsingPrimitivesWrappers.ReadBoolWrapper(ref ctx.buffer, ref ctx.state);
}
internal static uint? ReadUInt32Wrapper(ref ParseContext ctx)
{
return ParsingPrimitivesWrappers.ReadUInt32Wrapper(ref ctx.buffer, ref ctx.state);
}
internal static int? ReadInt32Wrapper(ref ParseContext ctx)
{
return ParsingPrimitivesWrappers.ReadInt32Wrapper(ref ctx.buffer, ref ctx.state);
}
internal static ulong? ReadUInt64Wrapper(ref ParseContext ctx)
{
return ParsingPrimitivesWrappers.ReadUInt64Wrapper(ref ctx.buffer, ref ctx.state);
}
internal static ulong? ReadUInt64WrapperSlow(ref ParseContext ctx)
{
return ParsingPrimitivesWrappers.ReadUInt64WrapperSlow(ref ctx.buffer, ref ctx.state);
}
internal static long? ReadInt64Wrapper(ref ParseContext ctx)
{
return ParsingPrimitivesWrappers.ReadInt64Wrapper(ref ctx.buffer, ref ctx.state);
}
}
}

View File

@ -0,0 +1,56 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System.Runtime.CompilerServices;
using System.Security;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
#if !NCRUNCH
[assembly: AllowPartiallyTrustedCallers]
#endif
[assembly: InternalsVisibleTo("Google.Protobuf.Test, PublicKey=" +
"002400000480000094000000060200000024000052534131000400000100010025800fbcfc63a1" +
"7c66b303aae80b03a6beaa176bb6bef883be436f2a1579edd80ce23edf151a1f4ced97af83abcd" +
"981207041fd5b2da3b498346fcfcd94910d52f25537c4a43ce3fbe17dc7d43e6cbdb4d8f1242dc" +
"b6bd9b5906be74da8daa7d7280f97130f318a16c07baf118839b156299a48522f9fae2371c9665" +
"c5ae9cb6")]
[assembly: InternalsVisibleTo("Google.Protobuf.Benchmarks, PublicKey=" +
"002400000480000094000000060200000024000052534131000400000100010025800fbcfc63a1" +
"7c66b303aae80b03a6beaa176bb6bef883be436f2a1579edd80ce23edf151a1f4ced97af83abcd" +
"981207041fd5b2da3b498346fcfcd94910d52f25537c4a43ce3fbe17dc7d43e6cbdb4d8f1242dc" +
"b6bd9b5906be74da8daa7d7280f97130f318a16c07baf118839b156299a48522f9fae2371c9665" +
"c5ae9cb6")]

View File

@ -0,0 +1,79 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
namespace LC.Google.Protobuf
{
/// <summary>
/// Helper methods for throwing exceptions when preconditions are not met.
/// </summary>
/// <remarks>
/// This class is used internally and by generated code; it is not particularly
/// expected to be used from application code, although nothing prevents it
/// from being used that way.
/// </remarks>
public static class ProtoPreconditions
{
/// <summary>
/// Throws an ArgumentNullException if the given value is null, otherwise
/// return the value to the caller.
/// </summary>
public static T CheckNotNull<T>(T value, string name) where T : class
{
if (value == null)
{
throw new ArgumentNullException(name);
}
return value;
}
/// <summary>
/// Throws an ArgumentNullException if the given value is null, otherwise
/// return the value to the caller.
/// </summary>
/// <remarks>
/// This is equivalent to <see cref="CheckNotNull{T}(T, string)"/> but without the type parameter
/// constraint. In most cases, the constraint is useful to prevent you from calling CheckNotNull
/// with a value type - but it gets in the way if either you want to use it with a nullable
/// value type, or you want to use it with an unconstrained type parameter.
/// </remarks>
internal static T CheckNotNullUnconstrained<T>(T value, string name)
{
if (value == null)
{
throw new ArgumentNullException(name);
}
return value;
}
}
}

View File

@ -0,0 +1,304 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2017 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using LC.Google.Protobuf.Collections;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace LC.Google.Protobuf.Reflection
{
/// <summary>
/// Container for a set of custom options specified within a message, field etc.
/// </summary>
/// <remarks>
/// <para>
/// This type is publicly immutable, but internally mutable. It is only populated
/// by the descriptor parsing code - by the time any user code is able to see an instance,
/// it will be fully initialized.
/// </para>
/// <para>
/// If an option is requested using the incorrect method, an answer may still be returned: all
/// of the numeric types are represented internally using 64-bit integers, for example. It is up to
/// the caller to ensure that they make the appropriate method call for the option they're interested in.
/// Note that enum options are simply stored as integers, so the value should be fetched using
/// <see cref="TryGetInt32(int, out int)"/> and then cast appropriately.
/// </para>
/// <para>
/// Repeated options are currently not supported. Asking for a single value of an option
/// which was actually repeated will return the last value, except for message types where
/// all the set values are merged together.
/// </para>
/// </remarks>
public sealed class CustomOptions
{
private static readonly object[] EmptyParameters = new object[0];
private readonly IDictionary<int, IExtensionValue> values;
internal CustomOptions(IDictionary<int, IExtensionValue> values)
{
this.values = values;
}
/// <summary>
/// Retrieves a Boolean value for the specified option field.
/// </summary>
/// <param name="field">The field to fetch the value for.</param>
/// <param name="value">The output variable to populate.</param>
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
public bool TryGetBool(int field, out bool value) => TryGetPrimitiveValue(field, out value);
/// <summary>
/// Retrieves a signed 32-bit integer value for the specified option field.
/// </summary>
/// <param name="field">The field to fetch the value for.</param>
/// <param name="value">The output variable to populate.</param>
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
public bool TryGetInt32(int field, out int value) => TryGetPrimitiveValue(field, out value);
/// <summary>
/// Retrieves a signed 64-bit integer value for the specified option field.
/// </summary>
/// <param name="field">The field to fetch the value for.</param>
/// <param name="value">The output variable to populate.</param>
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
public bool TryGetInt64(int field, out long value) => TryGetPrimitiveValue(field, out value);
/// <summary>
/// Retrieves an unsigned 32-bit integer value for the specified option field,
/// assuming a fixed-length representation.
/// </summary>
/// <param name="field">The field to fetch the value for.</param>
/// <param name="value">The output variable to populate.</param>
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
public bool TryGetFixed32(int field, out uint value) => TryGetUInt32(field, out value);
/// <summary>
/// Retrieves an unsigned 64-bit integer value for the specified option field,
/// assuming a fixed-length representation.
/// </summary>
/// <param name="field">The field to fetch the value for.</param>
/// <param name="value">The output variable to populate.</param>
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
public bool TryGetFixed64(int field, out ulong value) => TryGetUInt64(field, out value);
/// <summary>
/// Retrieves a signed 32-bit integer value for the specified option field,
/// assuming a fixed-length representation.
/// </summary>
/// <param name="field">The field to fetch the value for.</param>
/// <param name="value">The output variable to populate.</param>
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
public bool TryGetSFixed32(int field, out int value) => TryGetInt32(field, out value);
/// <summary>
/// Retrieves a signed 64-bit integer value for the specified option field,
/// assuming a fixed-length representation.
/// </summary>
/// <param name="field">The field to fetch the value for.</param>
/// <param name="value">The output variable to populate.</param>
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
public bool TryGetSFixed64(int field, out long value) => TryGetInt64(field, out value);
/// <summary>
/// Retrieves a signed 32-bit integer value for the specified option field,
/// assuming a zigzag encoding.
/// </summary>
/// <param name="field">The field to fetch the value for.</param>
/// <param name="value">The output variable to populate.</param>
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
public bool TryGetSInt32(int field, out int value) => TryGetPrimitiveValue(field, out value);
/// <summary>
/// Retrieves a signed 64-bit integer value for the specified option field,
/// assuming a zigzag encoding.
/// </summary>
/// <param name="field">The field to fetch the value for.</param>
/// <param name="value">The output variable to populate.</param>
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
public bool TryGetSInt64(int field, out long value) => TryGetPrimitiveValue(field, out value);
/// <summary>
/// Retrieves an unsigned 32-bit integer value for the specified option field.
/// </summary>
/// <param name="field">The field to fetch the value for.</param>
/// <param name="value">The output variable to populate.</param>
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
public bool TryGetUInt32(int field, out uint value) => TryGetPrimitiveValue(field, out value);
/// <summary>
/// Retrieves an unsigned 64-bit integer value for the specified option field.
/// </summary>
/// <param name="field">The field to fetch the value for.</param>
/// <param name="value">The output variable to populate.</param>
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
public bool TryGetUInt64(int field, out ulong value) => TryGetPrimitiveValue(field, out value);
/// <summary>
/// Retrieves a 32-bit floating point value for the specified option field.
/// </summary>
/// <param name="field">The field to fetch the value for.</param>
/// <param name="value">The output variable to populate.</param>
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
public bool TryGetFloat(int field, out float value) => TryGetPrimitiveValue(field, out value);
/// <summary>
/// Retrieves a 64-bit floating point value for the specified option field.
/// </summary>
/// <param name="field">The field to fetch the value for.</param>
/// <param name="value">The output variable to populate.</param>
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
public bool TryGetDouble(int field, out double value) => TryGetPrimitiveValue(field, out value);
/// <summary>
/// Retrieves a string value for the specified option field.
/// </summary>
/// <param name="field">The field to fetch the value for.</param>
/// <param name="value">The output variable to populate.</param>
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
public bool TryGetString(int field, out string value) => TryGetPrimitiveValue(field, out value);
/// <summary>
/// Retrieves a bytes value for the specified option field.
/// </summary>
/// <param name="field">The field to fetch the value for.</param>
/// <param name="value">The output variable to populate.</param>
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
public bool TryGetBytes(int field, out ByteString value) => TryGetPrimitiveValue(field, out value);
/// <summary>
/// Retrieves a message value for the specified option field.
/// </summary>
/// <param name="field">The field to fetch the value for.</param>
/// <param name="value">The output variable to populate.</param>
/// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
public bool TryGetMessage<T>(int field, out T value) where T : class, IMessage, new()
{
if (values == null)
{
value = default(T);
return false;
}
IExtensionValue extensionValue;
if (values.TryGetValue(field, out extensionValue))
{
if (extensionValue is ExtensionValue<T>)
{
ExtensionValue<T> single = extensionValue as ExtensionValue<T>;
ByteString bytes = single.GetValue().ToByteString();
value = new T();
value.MergeFrom(bytes);
return true;
}
else if (extensionValue is RepeatedExtensionValue<T>)
{
RepeatedExtensionValue<T> repeated = extensionValue as RepeatedExtensionValue<T>;
value = repeated.GetValue()
.Select(v => v.ToByteString())
.Aggregate(new T(), (t, b) =>
{
t.MergeFrom(b);
return t;
});
return true;
}
}
value = null;
return false;
}
private bool TryGetPrimitiveValue<T>(int field, out T value)
{
if (values == null)
{
value = default(T);
return false;
}
IExtensionValue extensionValue;
if (values.TryGetValue(field, out extensionValue))
{
if (extensionValue is ExtensionValue<T>)
{
ExtensionValue<T> single = extensionValue as ExtensionValue<T>;
value = single.GetValue();
return true;
}
else if (extensionValue is RepeatedExtensionValue<T>)
{
RepeatedExtensionValue<T> repeated = extensionValue as RepeatedExtensionValue<T>;
if (repeated.GetValue().Count != 0)
{
RepeatedField<T> repeatedField = repeated.GetValue();
value = repeatedField[repeatedField.Count - 1];
return true;
}
}
else // and here we find explicit enum handling since T : Enum ! x is ExtensionValue<Enum>
{
var type = extensionValue.GetType();
if (type.GetGenericTypeDefinition() == typeof(ExtensionValue<>))
{
var typeInfo = type.GetTypeInfo();
var typeArgs = typeInfo.GenericTypeArguments;
if (typeArgs.Length == 1 && typeArgs[0].GetTypeInfo().IsEnum)
{
value = (T)typeInfo.GetDeclaredMethod(nameof(ExtensionValue<T>.GetValue)).Invoke(extensionValue, EmptyParameters);
return true;
}
}
else if (type.GetGenericTypeDefinition() == typeof(RepeatedExtensionValue<>))
{
var typeInfo = type.GetTypeInfo();
var typeArgs = typeInfo.GenericTypeArguments;
if (typeArgs.Length == 1 && typeArgs[0].GetTypeInfo().IsEnum)
{
var values = (IList)typeInfo.GetDeclaredMethod(nameof(RepeatedExtensionValue<T>.GetValue)).Invoke(extensionValue, EmptyParameters);
if (values.Count != 0)
{
value = (T)values[values.Count - 1];
return true;
}
}
}
}
}
value = default(T);
return false;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,92 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System.Collections.Generic;
namespace LC.Google.Protobuf.Reflection
{
/// <summary>
/// Base class for nearly all descriptors, providing common functionality.
/// </summary>
public abstract class DescriptorBase : IDescriptor
{
internal DescriptorBase(FileDescriptor file, string fullName, int index)
{
File = file;
FullName = fullName;
Index = index;
}
/// <value>
/// The index of this descriptor within its parent descriptor.
/// </value>
/// <remarks>
/// This returns the index of this descriptor within its parent, for
/// this descriptor's type. (There can be duplicate values for different
/// types, e.g. one enum type with index 0 and one message type with index 0.)
/// </remarks>
public int Index { get; }
/// <summary>
/// Returns the name of the entity (field, message etc) being described.
/// </summary>
public abstract string Name { get; }
/// <summary>
/// The fully qualified name of the descriptor's target.
/// </summary>
public string FullName { get; }
/// <value>
/// The file this descriptor was declared in.
/// </value>
public FileDescriptor File { get; }
/// <summary>
/// The declaration information about the descriptor, or null if no declaration information
/// is available for this descriptor.
/// </summary>
/// <remarks>
/// This information is typically only available for dynamically loaded descriptors,
/// for example within a protoc plugin where the full descriptors, including source info,
/// are passed to the code by protoc.
/// </remarks>
public DescriptorDeclaration Declaration => File.GetDeclaration(this);
/// <summary>
/// Retrieves the list of nested descriptors corresponding to the given field number, if any.
/// If the field is unknown or not a nested descriptor list, return null to terminate the search.
/// The default implementation returns null.
/// </summary>
internal virtual IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber) => null;
}
}

View File

@ -0,0 +1,112 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2018 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using static LC.Google.Protobuf.Reflection.SourceCodeInfo.Types;
namespace LC.Google.Protobuf.Reflection
{
/// <summary>
/// Provides additional information about the declaration of a descriptor,
/// such as source location and comments.
/// </summary>
public sealed class DescriptorDeclaration
{
/// <summary>
/// The descriptor this declaration relates to.
/// </summary>
public IDescriptor Descriptor { get; }
/// <summary>
/// The start line of the declaration within the source file. This value is 1-based.
/// </summary>
public int StartLine { get; }
/// <summary>
/// The start column of the declaration within the source file. This value is 1-based.
/// </summary>
public int StartColumn { get; }
/// <summary>
/// // The end line of the declaration within the source file. This value is 1-based.
/// </summary>
public int EndLine { get; }
/// <summary>
/// The end column of the declaration within the source file. This value is 1-based, and
/// exclusive. (The final character of the declaration is on the column before this value.)
/// </summary>
public int EndColumn { get; }
/// <summary>
/// Comments appearing before the declaration. Never null, but may be empty. Multi-line comments
/// are represented as a newline-separated string. Leading whitespace and the comment marker ("//")
/// are removed from each line.
/// </summary>
public string LeadingComments { get; }
/// <summary>
/// Comments appearing after the declaration. Never null, but may be empty. Multi-line comments
/// are represented as a newline-separated string. Leading whitespace and the comment marker ("//")
/// are removed from each line.
/// </summary>
public string TrailingComments { get; }
/// <summary>
/// Comments appearing before the declaration, but separated from it by blank
/// lines. Each string represents a newline-separated paragraph of comments.
/// Leading whitespace and the comment marker ("//") are removed from each line.
/// The list is never null, but may be empty. Likewise each element is never null, but may be empty.
/// </summary>
public IReadOnlyList<string> LeadingDetachedComments { get; }
private DescriptorDeclaration(IDescriptor descriptor, Location location)
{
// TODO: Validation
Descriptor = descriptor;
bool hasEndLine = location.Span.Count == 4;
// Lines and columns are 0-based in the proto.
StartLine = location.Span[0] + 1;
StartColumn = location.Span[1] + 1;
EndLine = hasEndLine ? location.Span[2] + 1 : StartLine;
EndColumn = location.Span[hasEndLine ? 3 : 2] + 1;
LeadingComments = location.LeadingComments;
TrailingComments = location.TrailingComments;
LeadingDetachedComments = new ReadOnlyCollection<string>(location.LeadingDetachedComments.ToList());
}
internal static DescriptorDeclaration FromProto(IDescriptor descriptor, Location location) =>
new DescriptorDeclaration(descriptor, location);
}
}

View File

@ -0,0 +1,334 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
namespace LC.Google.Protobuf.Reflection
{
/// <summary>
/// Contains lookup tables containing all the descriptors defined in a particular file.
/// </summary>
internal sealed class DescriptorPool
{
private readonly IDictionary<string, IDescriptor> descriptorsByName =
new Dictionary<string, IDescriptor>();
private readonly IDictionary<ObjectIntPair<IDescriptor>, FieldDescriptor> fieldsByNumber =
new Dictionary<ObjectIntPair<IDescriptor>, FieldDescriptor>();
private readonly IDictionary<ObjectIntPair<IDescriptor>, EnumValueDescriptor> enumValuesByNumber =
new Dictionary<ObjectIntPair<IDescriptor>, EnumValueDescriptor>();
private readonly HashSet<FileDescriptor> dependencies;
internal DescriptorPool(IEnumerable<FileDescriptor> dependencyFiles)
{
dependencies = new HashSet<FileDescriptor>();
foreach (var dependencyFile in dependencyFiles)
{
dependencies.Add(dependencyFile);
ImportPublicDependencies(dependencyFile);
}
foreach (FileDescriptor dependency in dependencyFiles)
{
AddPackage(dependency.Package, dependency);
}
}
private void ImportPublicDependencies(FileDescriptor file)
{
foreach (FileDescriptor dependency in file.PublicDependencies)
{
if (dependencies.Add(dependency))
{
ImportPublicDependencies(dependency);
}
}
}
/// <summary>
/// Finds a symbol of the given name within the pool.
/// </summary>
/// <typeparam name="T">The type of symbol to look for</typeparam>
/// <param name="fullName">Fully-qualified name to look up</param>
/// <returns>The symbol with the given name and type,
/// or null if the symbol doesn't exist or has the wrong type</returns>
internal T FindSymbol<T>(string fullName) where T : class
{
IDescriptor result;
descriptorsByName.TryGetValue(fullName, out result);
T descriptor = result as T;
if (descriptor != null)
{
return descriptor;
}
// dependencies contains direct dependencies and any *public* dependencies
// of those dependencies (transitively)... so we don't need to recurse here.
foreach (FileDescriptor dependency in dependencies)
{
dependency.DescriptorPool.descriptorsByName.TryGetValue(fullName, out result);
descriptor = result as T;
if (descriptor != null)
{
return descriptor;
}
}
return null;
}
/// <summary>
/// Adds a package to the symbol tables. If a package by the same name
/// already exists, that is fine, but if some other kind of symbol
/// exists under the same name, an exception is thrown. If the package
/// has multiple components, this also adds the parent package(s).
/// </summary>
internal void AddPackage(string fullName, FileDescriptor file)
{
int dotpos = fullName.LastIndexOf('.');
String name;
if (dotpos != -1)
{
AddPackage(fullName.Substring(0, dotpos), file);
name = fullName.Substring(dotpos + 1);
}
else
{
name = fullName;
}
IDescriptor old;
if (descriptorsByName.TryGetValue(fullName, out old))
{
if (!(old is PackageDescriptor))
{
throw new DescriptorValidationException(file,
"\"" + name +
"\" is already defined (as something other than a " +
"package) in file \"" + old.File.Name + "\".");
}
}
descriptorsByName[fullName] = new PackageDescriptor(name, fullName, file);
}
/// <summary>
/// Adds a symbol to the symbol table.
/// </summary>
/// <exception cref="DescriptorValidationException">The symbol already existed
/// in the symbol table.</exception>
internal void AddSymbol(IDescriptor descriptor)
{
ValidateSymbolName(descriptor);
String fullName = descriptor.FullName;
IDescriptor old;
if (descriptorsByName.TryGetValue(fullName, out old))
{
int dotPos = fullName.LastIndexOf('.');
string message;
if (descriptor.File == old.File)
{
if (dotPos == -1)
{
message = "\"" + fullName + "\" is already defined.";
}
else
{
message = "\"" + fullName.Substring(dotPos + 1) + "\" is already defined in \"" +
fullName.Substring(0, dotPos) + "\".";
}
}
else
{
message = "\"" + fullName + "\" is already defined in file \"" + old.File.Name + "\".";
}
throw new DescriptorValidationException(descriptor, message);
}
descriptorsByName[fullName] = descriptor;
}
private static readonly Regex ValidationRegex = new Regex("^[_A-Za-z][_A-Za-z0-9]*$",
FrameworkPortability.CompiledRegexWhereAvailable);
/// <summary>
/// Verifies that the descriptor's name is valid (i.e. it contains
/// only letters, digits and underscores, and does not start with a digit).
/// </summary>
/// <param name="descriptor"></param>
private static void ValidateSymbolName(IDescriptor descriptor)
{
if (descriptor.Name == "")
{
throw new DescriptorValidationException(descriptor, "Missing name.");
}
if (!ValidationRegex.IsMatch(descriptor.Name))
{
throw new DescriptorValidationException(descriptor,
"\"" + descriptor.Name + "\" is not a valid identifier.");
}
}
/// <summary>
/// Returns the field with the given number in the given descriptor,
/// or null if it can't be found.
/// </summary>
internal FieldDescriptor FindFieldByNumber(MessageDescriptor messageDescriptor, int number)
{
FieldDescriptor ret;
fieldsByNumber.TryGetValue(new ObjectIntPair<IDescriptor>(messageDescriptor, number), out ret);
return ret;
}
internal EnumValueDescriptor FindEnumValueByNumber(EnumDescriptor enumDescriptor, int number)
{
EnumValueDescriptor ret;
enumValuesByNumber.TryGetValue(new ObjectIntPair<IDescriptor>(enumDescriptor, number), out ret);
return ret;
}
/// <summary>
/// Adds a field to the fieldsByNumber table.
/// </summary>
/// <exception cref="DescriptorValidationException">A field with the same
/// containing type and number already exists.</exception>
internal void AddFieldByNumber(FieldDescriptor field)
{
// for extensions, we use the extended type, otherwise we use the containing type
ObjectIntPair<IDescriptor> key = new ObjectIntPair<IDescriptor>(field.Proto.HasExtendee ? field.ExtendeeType : field.ContainingType, field.FieldNumber);
FieldDescriptor old;
if (fieldsByNumber.TryGetValue(key, out old))
{
throw new DescriptorValidationException(field, "Field number " + field.FieldNumber +
"has already been used in \"" +
field.ContainingType.FullName +
"\" by field \"" + old.Name + "\".");
}
fieldsByNumber[key] = field;
}
/// <summary>
/// Adds an enum value to the enumValuesByNumber table. If an enum value
/// with the same type and number already exists, this method does nothing.
/// (This is allowed; the first value defined with the number takes precedence.)
/// </summary>
internal void AddEnumValueByNumber(EnumValueDescriptor enumValue)
{
ObjectIntPair<IDescriptor> key = new ObjectIntPair<IDescriptor>(enumValue.EnumDescriptor, enumValue.Number);
if (!enumValuesByNumber.ContainsKey(key))
{
enumValuesByNumber[key] = enumValue;
}
}
/// <summary>
/// Looks up a descriptor by name, relative to some other descriptor.
/// The name may be fully-qualified (with a leading '.'), partially-qualified,
/// or unqualified. C++-like name lookup semantics are used to search for the
/// matching descriptor.
/// </summary>
/// <remarks>
/// This isn't heavily optimized, but it's only used during cross linking anyway.
/// If it starts being used more widely, we should look at performance more carefully.
/// </remarks>
internal IDescriptor LookupSymbol(string name, IDescriptor relativeTo)
{
IDescriptor result;
if (name.StartsWith("."))
{
// Fully-qualified name.
result = FindSymbol<IDescriptor>(name.Substring(1));
}
else
{
// If "name" is a compound identifier, we want to search for the
// first component of it, then search within it for the rest.
int firstPartLength = name.IndexOf('.');
string firstPart = firstPartLength == -1 ? name : name.Substring(0, firstPartLength);
// We will search each parent scope of "relativeTo" looking for the
// symbol.
StringBuilder scopeToTry = new StringBuilder(relativeTo.FullName);
while (true)
{
// Chop off the last component of the scope.
int dotpos = scopeToTry.ToString().LastIndexOf(".");
if (dotpos == -1)
{
result = FindSymbol<IDescriptor>(name);
break;
}
else
{
scopeToTry.Length = dotpos + 1;
// Append firstPart and try to find.
scopeToTry.Append(firstPart);
result = FindSymbol<IDescriptor>(scopeToTry.ToString());
if (result != null)
{
if (firstPartLength != -1)
{
// We only found the first part of the symbol. Now look for
// the whole thing. If this fails, we *don't* want to keep
// searching parent scopes.
scopeToTry.Length = dotpos + 1;
scopeToTry.Append(name);
result = FindSymbol<IDescriptor>(scopeToTry.ToString());
}
break;
}
// Not found. Remove the name so we can try again.
scopeToTry.Length = dotpos;
}
}
}
if (result == null)
{
throw new DescriptorValidationException(relativeTo, "\"" + name + "\" is not defined.");
}
else
{
return result;
}
}
}
}

View File

@ -0,0 +1,64 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace LC.Google.Protobuf.Reflection
{
/// <summary>
/// Internal class containing utility methods when working with descriptors.
/// </summary>
internal static class DescriptorUtil
{
/// <summary>
/// Equivalent to Func[TInput, int, TOutput] but usable in .NET 2.0. Only used to convert
/// arrays.
/// </summary>
internal delegate TOutput IndexedConverter<TInput, TOutput>(TInput element, int index);
/// <summary>
/// Converts the given array into a read-only list, applying the specified conversion to
/// each input element.
/// </summary>
internal static IList<TOutput> ConvertAndMakeReadOnly<TInput, TOutput>
(IList<TInput> input, IndexedConverter<TInput, TOutput> converter)
{
TOutput[] array = new TOutput[input.Count];
for (int i = 0; i < array.Length; i++)
{
array[i] = converter(input[i], i);
}
return new ReadOnlyCollection<TOutput>(array);
}
}
}

View File

@ -0,0 +1,80 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
namespace LC.Google.Protobuf.Reflection
{
/// <summary>
/// Thrown when building descriptors fails because the source DescriptorProtos
/// are not valid.
/// </summary>
public sealed class DescriptorValidationException : Exception
{
private readonly String name;
private readonly string description;
/// <value>
/// The full name of the descriptor where the error occurred.
/// </value>
public String ProblemSymbolName
{
get { return name; }
}
/// <value>
/// A human-readable description of the error. (The Message property
/// is made up of the descriptor's name and this description.)
/// </value>
public string Description
{
get { return description; }
}
internal DescriptorValidationException(IDescriptor problemDescriptor, string description) :
base(problemDescriptor.FullName + ": " + description)
{
// Note that problemDescriptor may be partially uninitialized, so we
// don't want to expose it directly to the user. So, we only provide
// the name and the original proto.
name = problemDescriptor.FullName;
this.description = description;
}
internal DescriptorValidationException(IDescriptor problemDescriptor, string description, Exception cause) :
base(problemDescriptor.FullName + ": " + description, cause)
{
name = problemDescriptor.FullName;
this.description = description;
}
}
}

View File

@ -0,0 +1,161 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using LC.Google.Protobuf.Collections;
using System;
using System.Collections.Generic;
namespace LC.Google.Protobuf.Reflection
{
/// <summary>
/// Descriptor for an enum type in a .proto file.
/// </summary>
public sealed class EnumDescriptor : DescriptorBase
{
private readonly EnumDescriptorProto proto;
private readonly MessageDescriptor containingType;
private readonly IList<EnumValueDescriptor> values;
private readonly Type clrType;
internal EnumDescriptor(EnumDescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int index, Type clrType)
: base(file, file.ComputeFullName(parent, proto.Name), index)
{
this.proto = proto;
this.clrType = clrType;
containingType = parent;
if (proto.Value.Count == 0)
{
// We cannot allow enums with no values because this would mean there
// would be no valid default value for fields of this type.
throw new DescriptorValidationException(this, "Enums must contain at least one value.");
}
values = DescriptorUtil.ConvertAndMakeReadOnly(proto.Value,
(value, i) => new EnumValueDescriptor(value, file, this, i));
File.DescriptorPool.AddSymbol(this);
}
internal EnumDescriptorProto Proto { get { return proto; } }
/// <summary>
/// The brief name of the descriptor's target.
/// </summary>
public override string Name { get { return proto.Name; } }
internal override IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber)
{
switch (fieldNumber)
{
case EnumDescriptorProto.ValueFieldNumber:
return (IReadOnlyList<DescriptorBase>) Values;
default:
return null;
}
}
/// <summary>
/// The CLR type for this enum. For generated code, this will be a CLR enum type.
/// </summary>
public Type ClrType { get { return clrType; } }
/// <value>
/// If this is a nested type, get the outer descriptor, otherwise null.
/// </value>
public MessageDescriptor ContainingType
{
get { return containingType; }
}
/// <value>
/// An unmodifiable list of defined value descriptors for this enum.
/// </value>
public IList<EnumValueDescriptor> Values
{
get { return values; }
}
/// <summary>
/// Finds an enum value by number. If multiple enum values have the
/// same number, this returns the first defined value with that number.
/// If there is no value for the given number, this returns <c>null</c>.
/// </summary>
public EnumValueDescriptor FindValueByNumber(int number)
{
return File.DescriptorPool.FindEnumValueByNumber(this, number);
}
/// <summary>
/// Finds an enum value by name.
/// </summary>
/// <param name="name">The unqualified name of the value (e.g. "FOO").</param>
/// <returns>The value's descriptor, or null if not found.</returns>
public EnumValueDescriptor FindValueByName(string name)
{
return File.DescriptorPool.FindSymbol<EnumValueDescriptor>(FullName + "." + name);
}
/// <summary>
/// The (possibly empty) set of custom options for this enum.
/// </summary>
[Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")]
public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
/// <summary>
/// The <c>EnumOptions</c>, defined in <c>descriptor.proto</c>.
/// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
/// Custom options can be retrieved as extensions of the returned message.
/// NOTE: A defensive copy is created each time this property is retrieved.
/// </summary>
public EnumOptions GetOptions() => Proto.Options?.Clone();
/// <summary>
/// Gets a single value enum option for this descriptor
/// </summary>
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
public T GetOption<T>(Extension<EnumOptions, T> extension)
{
var value = Proto.Options.GetExtension(extension);
return value is IDeepCloneable<T> ? (value as IDeepCloneable<T>).Clone() : value;
}
/// <summary>
/// Gets a repeated value enum option for this descriptor
/// </summary>
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
public RepeatedField<T> GetOption<T>(RepeatedExtension<EnumOptions, T> extension)
{
return Proto.Options.GetExtension(extension).Clone();
}
}
}

View File

@ -0,0 +1,107 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using LC.Google.Protobuf.Collections;
using System;
namespace LC.Google.Protobuf.Reflection
{
/// <summary>
/// Descriptor for a single enum value within an enum in a .proto file.
/// </summary>
public sealed class EnumValueDescriptor : DescriptorBase
{
private readonly EnumDescriptor enumDescriptor;
private readonly EnumValueDescriptorProto proto;
internal EnumValueDescriptor(EnumValueDescriptorProto proto, FileDescriptor file,
EnumDescriptor parent, int index)
: base(file, parent.FullName + "." + proto.Name, index)
{
this.proto = proto;
enumDescriptor = parent;
file.DescriptorPool.AddSymbol(this);
file.DescriptorPool.AddEnumValueByNumber(this);
}
internal EnumValueDescriptorProto Proto { get { return proto; } }
/// <summary>
/// Returns the name of the enum value described by this object.
/// </summary>
public override string Name { get { return proto.Name; } }
/// <summary>
/// Returns the number associated with this enum value.
/// </summary>
public int Number { get { return Proto.Number; } }
/// <summary>
/// Returns the enum descriptor that this value is part of.
/// </summary>
public EnumDescriptor EnumDescriptor { get { return enumDescriptor; } }
/// <summary>
/// The (possibly empty) set of custom options for this enum value.
/// </summary>
[Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")]
public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
/// <summary>
/// The <c>EnumValueOptions</c>, defined in <c>descriptor.proto</c>.
/// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
/// Custom options can be retrieved as extensions of the returned message.
/// NOTE: A defensive copy is created each time this property is retrieved.
/// </summary>
public EnumValueOptions GetOptions() => Proto.Options?.Clone();
/// <summary>
/// Gets a single value enum value option for this descriptor
/// </summary>
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
public T GetOption<T>(Extension<EnumValueOptions, T> extension)
{
var value = Proto.Options.GetExtension(extension);
return value is IDeepCloneable<T> ? (value as IDeepCloneable<T>).Clone() : value;
}
/// <summary>
/// Gets a repeated value enum value option for this descriptor
/// </summary>
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
public RepeatedField<T> GetOption<T>(RepeatedExtension<EnumValueOptions, T> extension)
{
return Proto.Options.GetExtension(extension).Clone();
}
}
}

View File

@ -0,0 +1,69 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
namespace LC.Google.Protobuf.Reflection
{
internal sealed class ExtensionAccessor : IFieldAccessor
{
private readonly Extension extension;
private readonly ReflectionUtil.IExtensionReflectionHelper helper;
internal ExtensionAccessor(FieldDescriptor descriptor)
{
Descriptor = descriptor;
extension = descriptor.Extension;
helper = ReflectionUtil.CreateExtensionHelper(extension);
}
public FieldDescriptor Descriptor { get; }
public void Clear(IMessage message)
{
helper.ClearExtension(message);
}
public bool HasValue(IMessage message)
{
return helper.HasExtension(message);
}
public object GetValue(IMessage message)
{
return helper.GetExtension(message);
}
public void SetValue(IMessage message, object value)
{
helper.SetExtension(message, value);
}
}
}

View File

@ -0,0 +1,126 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace LC.Google.Protobuf.Reflection
{
/// <summary>
/// A collection to simplify retrieving the descriptors of extensions in a descriptor for a message
/// </summary>
public sealed class ExtensionCollection
{
private IDictionary<MessageDescriptor, IList<FieldDescriptor>> extensionsByTypeInDeclarationOrder;
private IDictionary<MessageDescriptor, IList<FieldDescriptor>> extensionsByTypeInNumberOrder;
internal ExtensionCollection(FileDescriptor file, Extension[] extensions)
{
UnorderedExtensions = DescriptorUtil.ConvertAndMakeReadOnly(
file.Proto.Extension,
(extension, i) => {
if (extensions?.Length != 0)
{
return new FieldDescriptor(extension, file, null, i, null, extensions?[i]);
}
else
{
return new FieldDescriptor(extension, file, null, i, null, null); // return null if there's no extensions in this array for old code-gen
}
});
}
internal ExtensionCollection(MessageDescriptor message, Extension[] extensions)
{
UnorderedExtensions = DescriptorUtil.ConvertAndMakeReadOnly(
message.Proto.Extension,
(extension, i) => {
if (extensions?.Length != 0)
{
return new FieldDescriptor(extension, message.File, message, i, null, extensions?[i]);
}
else
{
return new FieldDescriptor(extension, message.File, message, i, null, null);
}
});
}
/// <summary>
/// Returns a readonly list of all the extensions defined in this type in
/// the order they were defined in the source .proto file
/// </summary>
public IList<FieldDescriptor> UnorderedExtensions { get; }
/// <summary>
/// Returns a readonly list of all the extensions define in this type that extend
/// the provided descriptor type in the order they were defined in the source .proto file
/// </summary>
public IList<FieldDescriptor> GetExtensionsInDeclarationOrder(MessageDescriptor descriptor)
{
return extensionsByTypeInDeclarationOrder[descriptor];
}
/// <summary>
/// Returns a readonly list of all the extensions define in this type that extend
/// the provided descriptor type in accending field order
/// </summary>
public IList<FieldDescriptor> GetExtensionsInNumberOrder(MessageDescriptor descriptor)
{
return extensionsByTypeInNumberOrder[descriptor];
}
internal void CrossLink()
{
Dictionary<MessageDescriptor, IList<FieldDescriptor>> declarationOrder = new Dictionary<MessageDescriptor, IList<FieldDescriptor>>();
foreach (FieldDescriptor descriptor in UnorderedExtensions)
{
descriptor.CrossLink();
IList<FieldDescriptor> list;
if (!declarationOrder.TryGetValue(descriptor.ExtendeeType, out list))
{
list = new List<FieldDescriptor>();
declarationOrder.Add(descriptor.ExtendeeType, list);
}
list.Add(descriptor);
}
extensionsByTypeInDeclarationOrder = declarationOrder
.ToDictionary(kvp => kvp.Key, kvp => (IList<FieldDescriptor>)new ReadOnlyCollection<FieldDescriptor>(kvp.Value));
extensionsByTypeInNumberOrder = declarationOrder
.ToDictionary(kvp => kvp.Key, kvp => (IList<FieldDescriptor>)new ReadOnlyCollection<FieldDescriptor>(kvp.Value.OrderBy(field => field.FieldNumber).ToArray()));
}
}
}

View File

@ -0,0 +1,64 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Reflection;
using LC.Google.Protobuf.Compatibility;
namespace LC.Google.Protobuf.Reflection
{
/// <summary>
/// Base class for field accessors.
/// </summary>
internal abstract class FieldAccessorBase : IFieldAccessor
{
private readonly Func<IMessage, object> getValueDelegate;
private readonly FieldDescriptor descriptor;
internal FieldAccessorBase(PropertyInfo property, FieldDescriptor descriptor)
{
this.descriptor = descriptor;
getValueDelegate = ReflectionUtil.CreateFuncIMessageObject(property.GetGetMethod());
}
public FieldDescriptor Descriptor { get { return descriptor; } }
public object GetValue(IMessage message)
{
return getValueDelegate(message);
}
public abstract bool HasValue(IMessage message);
public abstract void Clear(IMessage message);
public abstract void SetValue(IMessage message, object value);
}
}

View File

@ -0,0 +1,455 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using LC.Google.Protobuf.Collections;
using LC.Google.Protobuf.Compatibility;
using System;
namespace LC.Google.Protobuf.Reflection
{
/// <summary>
/// Descriptor for a field or extension within a message in a .proto file.
/// </summary>
public sealed class FieldDescriptor : DescriptorBase, IComparable<FieldDescriptor>
{
private EnumDescriptor enumType;
private MessageDescriptor extendeeType;
private MessageDescriptor messageType;
private FieldType fieldType;
private readonly string propertyName; // Annoyingly, needed in Crosslink.
private IFieldAccessor accessor;
/// <summary>
/// Get the field's containing message type, or <c>null</c> if it is a field defined at the top level of a file as an extension.
/// </summary>
public MessageDescriptor ContainingType { get; }
/// <summary>
/// Returns the oneof containing this field, or <c>null</c> if it is not part of a oneof.
/// </summary>
public OneofDescriptor ContainingOneof { get; }
/// <summary>
/// Returns the oneof containing this field if it's a "real" oneof, or <c>null</c> if either this
/// field is not part of a oneof, or the oneof is synthetic.
/// </summary>
public OneofDescriptor RealContainingOneof => ContainingOneof?.IsSynthetic == false ? ContainingOneof : null;
/// <summary>
/// The effective JSON name for this field. This is usually the lower-camel-cased form of the field name,
/// but can be overridden using the <c>json_name</c> option in the .proto file.
/// </summary>
public string JsonName { get; }
/// <summary>
/// Indicates whether this field supports presence, either implicitly (e.g. due to it being a message
/// type field) or explicitly via Has/Clear members. If this returns true, it is safe to call
/// <see cref="IFieldAccessor.Clear(IMessage)"/> and <see cref="IFieldAccessor.HasValue(IMessage)"/>
/// on this field's accessor with a suitable message.
/// </summary>
public bool HasPresence =>
Extension != null ? !Extension.IsRepeated
: IsRepeated ? false
: IsMap ? false
: FieldType == FieldType.Message ? true
// This covers "real oneof members" and "proto3 optional fields"
: ContainingOneof != null ? true
: File.Syntax == Syntax.Proto2;
internal FieldDescriptorProto Proto { get; }
/// <summary>
/// An extension identifier for this field, or <c>null</c> if this field isn't an extension.
/// </summary>
public Extension Extension { get; }
internal FieldDescriptor(FieldDescriptorProto proto, FileDescriptor file,
MessageDescriptor parent, int index, string propertyName, Extension extension)
: base(file, file.ComputeFullName(parent, proto.Name), index)
{
Proto = proto;
if (proto.Type != 0)
{
fieldType = GetFieldTypeFromProtoType(proto.Type);
}
if (FieldNumber <= 0)
{
throw new DescriptorValidationException(this, "Field numbers must be positive integers.");
}
ContainingType = parent;
if (proto.HasOneofIndex)
{
if (proto.OneofIndex < 0 || proto.OneofIndex >= parent.Proto.OneofDecl.Count)
{
throw new DescriptorValidationException(this,
$"FieldDescriptorProto.oneof_index is out of range for type {parent.Name}");
}
ContainingOneof = parent.Oneofs[proto.OneofIndex];
}
file.DescriptorPool.AddSymbol(this);
// We can't create the accessor until we've cross-linked, unfortunately, as we
// may not know whether the type of the field is a map or not. Remember the property name
// for later.
// We could trust the generated code and check whether the type of the property is
// a MapField, but that feels a tad nasty.
this.propertyName = propertyName;
Extension = extension;
JsonName = Proto.JsonName == "" ? JsonFormatter.ToJsonName(Proto.Name) : Proto.JsonName;
}
/// <summary>
/// The brief name of the descriptor's target.
/// </summary>
public override string Name => Proto.Name;
/// <summary>
/// Returns the accessor for this field.
/// </summary>
/// <remarks>
/// <para>
/// While a <see cref="FieldDescriptor"/> describes the field, it does not provide
/// any way of obtaining or changing the value of the field within a specific message;
/// that is the responsibility of the accessor.
/// </para>
/// <para>
/// In descriptors for generated code, the value returned by this property will be non-null for all
/// regular fields. However, if a message containing a map field is introspected, the list of nested messages will include
/// an auto-generated nested key/value pair message for the field. This is not represented in any
/// generated type, and the value of the map field itself is represented by a dictionary in the
/// reflection API. There are never instances of those "hidden" messages, so no accessor is provided
/// and this property will return null.
/// </para>
/// <para>
/// In dynamically loaded descriptors, the value returned by this property will current be null;
/// if and when dynamic messages are supported, it will return a suitable accessor to work with
/// them.
/// </para>
/// </remarks>
public IFieldAccessor Accessor => accessor;
/// <summary>
/// Maps a field type as included in the .proto file to a FieldType.
/// </summary>
private static FieldType GetFieldTypeFromProtoType(FieldDescriptorProto.Types.Type type)
{
switch (type)
{
case FieldDescriptorProto.Types.Type.Double:
return FieldType.Double;
case FieldDescriptorProto.Types.Type.Float:
return FieldType.Float;
case FieldDescriptorProto.Types.Type.Int64:
return FieldType.Int64;
case FieldDescriptorProto.Types.Type.Uint64:
return FieldType.UInt64;
case FieldDescriptorProto.Types.Type.Int32:
return FieldType.Int32;
case FieldDescriptorProto.Types.Type.Fixed64:
return FieldType.Fixed64;
case FieldDescriptorProto.Types.Type.Fixed32:
return FieldType.Fixed32;
case FieldDescriptorProto.Types.Type.Bool:
return FieldType.Bool;
case FieldDescriptorProto.Types.Type.String:
return FieldType.String;
case FieldDescriptorProto.Types.Type.Group:
return FieldType.Group;
case FieldDescriptorProto.Types.Type.Message:
return FieldType.Message;
case FieldDescriptorProto.Types.Type.Bytes:
return FieldType.Bytes;
case FieldDescriptorProto.Types.Type.Uint32:
return FieldType.UInt32;
case FieldDescriptorProto.Types.Type.Enum:
return FieldType.Enum;
case FieldDescriptorProto.Types.Type.Sfixed32:
return FieldType.SFixed32;
case FieldDescriptorProto.Types.Type.Sfixed64:
return FieldType.SFixed64;
case FieldDescriptorProto.Types.Type.Sint32:
return FieldType.SInt32;
case FieldDescriptorProto.Types.Type.Sint64:
return FieldType.SInt64;
default:
throw new ArgumentException("Invalid type specified");
}
}
/// <summary>
/// Returns <c>true</c> if this field is a repeated field; <c>false</c> otherwise.
/// </summary>
public bool IsRepeated => Proto.Label == FieldDescriptorProto.Types.Label.Repeated;
/// <summary>
/// Returns <c>true</c> if this field is a required field; <c>false</c> otherwise.
/// </summary>
public bool IsRequired => Proto.Label == FieldDescriptorProto.Types.Label.Required;
/// <summary>
/// Returns <c>true</c> if this field is a map field; <c>false</c> otherwise.
/// </summary>
public bool IsMap => fieldType == FieldType.Message && messageType.Proto.Options != null && messageType.Proto.Options.MapEntry;
/// <summary>
/// Returns <c>true</c> if this field is a packed, repeated field; <c>false</c> otherwise.
/// </summary>
public bool IsPacked
{
get
{
if (File.Syntax != Syntax.Proto3)
{
return Proto.Options?.Packed ?? false;
}
else
{
return !Proto.Options.HasPacked || Proto.Options.Packed;
}
}
}
/// <summary>
/// Returns <c>true</c> if this field extends another message type; <c>false</c> otherwise.
/// </summary>
public bool IsExtension => Proto.HasExtendee;
/// <summary>
/// Returns the type of the field.
/// </summary>
public FieldType FieldType => fieldType;
/// <summary>
/// Returns the field number declared in the proto file.
/// </summary>
public int FieldNumber => Proto.Number;
/// <summary>
/// Compares this descriptor with another one, ordering in "canonical" order
/// which simply means ascending order by field number. <paramref name="other"/>
/// must be a field of the same type, i.e. the <see cref="ContainingType"/> of
/// both fields must be the same.
/// </summary>
public int CompareTo(FieldDescriptor other)
{
if (other.ContainingType != ContainingType)
{
throw new ArgumentException("FieldDescriptors can only be compared to other FieldDescriptors " +
"for fields of the same message type.");
}
return FieldNumber - other.FieldNumber;
}
/// <summary>
/// For enum fields, returns the field's type.
/// </summary>
public EnumDescriptor EnumType
{
get
{
if (fieldType != FieldType.Enum)
{
throw new InvalidOperationException("EnumType is only valid for enum fields.");
}
return enumType;
}
}
/// <summary>
/// For embedded message and group fields, returns the field's type.
/// </summary>
public MessageDescriptor MessageType
{
get
{
if (fieldType != FieldType.Message && fieldType != FieldType.Group)
{
throw new InvalidOperationException("MessageType is only valid for message or group fields.");
}
return messageType;
}
}
/// <summary>
/// For extension fields, returns the extended type
/// </summary>
public MessageDescriptor ExtendeeType
{
get
{
if (!Proto.HasExtendee)
{
throw new InvalidOperationException("ExtendeeType is only valid for extension fields.");
}
return extendeeType;
}
}
/// <summary>
/// The (possibly empty) set of custom options for this field.
/// </summary>
[Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")]
public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
/// <summary>
/// The <c>FieldOptions</c>, defined in <c>descriptor.proto</c>.
/// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
/// Custom options can be retrieved as extensions of the returned message.
/// NOTE: A defensive copy is created each time this property is retrieved.
/// </summary>
public FieldOptions GetOptions() => Proto.Options?.Clone();
/// <summary>
/// Gets a single value field option for this descriptor
/// </summary>
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
public T GetOption<T>(Extension<FieldOptions, T> extension)
{
var value = Proto.Options.GetExtension(extension);
return value is IDeepCloneable<T> ? (value as IDeepCloneable<T>).Clone() : value;
}
/// <summary>
/// Gets a repeated value field option for this descriptor
/// </summary>
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
public RepeatedField<T> GetOption<T>(RepeatedExtension<FieldOptions, T> extension)
{
return Proto.Options.GetExtension(extension).Clone();
}
/// <summary>
/// Look up and cross-link all field types etc.
/// </summary>
internal void CrossLink()
{
if (Proto.HasTypeName)
{
IDescriptor typeDescriptor =
File.DescriptorPool.LookupSymbol(Proto.TypeName, this);
if (Proto.HasType)
{
// Choose field type based on symbol.
if (typeDescriptor is MessageDescriptor)
{
fieldType = FieldType.Message;
}
else if (typeDescriptor is EnumDescriptor)
{
fieldType = FieldType.Enum;
}
else
{
throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not a type.");
}
}
if (fieldType == FieldType.Message || fieldType == FieldType.Group)
{
if (!(typeDescriptor is MessageDescriptor))
{
throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not a message type.");
}
messageType = (MessageDescriptor) typeDescriptor;
if (Proto.HasDefaultValue)
{
throw new DescriptorValidationException(this, "Messages can't have default values.");
}
}
else if (fieldType == FieldType.Enum)
{
if (!(typeDescriptor is EnumDescriptor))
{
throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not an enum type.");
}
enumType = (EnumDescriptor) typeDescriptor;
}
else
{
throw new DescriptorValidationException(this, "Field with primitive type has type_name.");
}
}
else
{
if (fieldType == FieldType.Message || fieldType == FieldType.Enum)
{
throw new DescriptorValidationException(this, "Field with message or enum type missing type_name.");
}
}
if (Proto.HasExtendee)
{
extendeeType = File.DescriptorPool.LookupSymbol(Proto.Extendee, this) as MessageDescriptor;
}
// Note: no attempt to perform any default value parsing
File.DescriptorPool.AddFieldByNumber(this);
if (ContainingType != null && ContainingType.Proto.Options != null && ContainingType.Proto.Options.MessageSetWireFormat)
{
throw new DescriptorValidationException(this, "MessageSet format is not supported.");
}
accessor = CreateAccessor();
}
private IFieldAccessor CreateAccessor()
{
if (Extension != null)
{
return new ExtensionAccessor(this);
}
// If we're given no property name, that's because we really don't want an accessor.
// This could be because it's a map message, or it could be that we're loading a FileDescriptor dynamically.
// TODO: Support dynamic messages.
if (propertyName == null)
{
return null;
}
var property = ContainingType.ClrType.GetProperty(propertyName);
if (property == null)
{
throw new DescriptorValidationException(this, $"Property {propertyName} not found in {ContainingType.ClrType}");
}
return IsMap ? new MapFieldAccessor(property, this)
: IsRepeated ? new RepeatedFieldAccessor(property, this)
: (IFieldAccessor) new SingleFieldAccessor(property, this);
}
}
}

View File

@ -0,0 +1,113 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
namespace LC.Google.Protobuf.Reflection
{
/// <summary>
/// Enumeration of all the possible field types.
/// </summary>
public enum FieldType
{
/// <summary>
/// The <c>double</c> field type.
/// </summary>
Double,
/// <summary>
/// The <c>float</c> field type.
/// </summary>
Float,
/// <summary>
/// The <c>int64</c> field type.
/// </summary>
Int64,
/// <summary>
/// The <c>uint64</c> field type.
/// </summary>
UInt64,
/// <summary>
/// The <c>int32</c> field type.
/// </summary>
Int32,
/// <summary>
/// The <c>fixed64</c> field type.
/// </summary>
Fixed64,
/// <summary>
/// The <c>fixed32</c> field type.
/// </summary>
Fixed32,
/// <summary>
/// The <c>bool</c> field type.
/// </summary>
Bool,
/// <summary>
/// The <c>string</c> field type.
/// </summary>
String,
/// <summary>
/// The field type used for groups.
/// </summary>
Group,
/// <summary>
/// The field type used for message fields.
/// </summary>
Message,
/// <summary>
/// The <c>bytes</c> field type.
/// </summary>
Bytes,
/// <summary>
/// The <c>uint32</c> field type.
/// </summary>
UInt32,
/// <summary>
/// The <c>sfixed32</c> field type.
/// </summary>
SFixed32,
/// <summary>
/// The <c>sfixed64</c> field type.
/// </summary>
SFixed64,
/// <summary>
/// The <c>sint32</c> field type.
/// </summary>
SInt32,
/// <summary>
/// The <c>sint64</c> field type.
/// </summary>
SInt64,
/// <summary>
/// The field type used for enum fields.
/// </summary>
Enum
}
}

View File

@ -0,0 +1,593 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using LC.Google.Protobuf.Collections;
using LC.Google.Protobuf.WellKnownTypes;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using static LC.Google.Protobuf.Reflection.SourceCodeInfo.Types;
namespace LC.Google.Protobuf.Reflection
{
/// <summary>
/// The syntax of a .proto file
/// </summary>
public enum Syntax
{
/// <summary>
/// Proto2 syntax
/// </summary>
Proto2,
/// <summary>
/// Proto3 syntax
/// </summary>
Proto3,
/// <summary>
/// An unknown declared syntax
/// </summary>
Unknown
}
/// <summary>
/// Describes a .proto file, including everything defined within.
/// IDescriptor is implemented such that the File property returns this descriptor,
/// and the FullName is the same as the Name.
/// </summary>
public sealed class FileDescriptor : IDescriptor
{
// Prevent linker failures when using IL2CPP with the well-known types.
static FileDescriptor()
{
ForceReflectionInitialization<Syntax>();
ForceReflectionInitialization<NullValue>();
ForceReflectionInitialization<Field.Types.Cardinality>();
ForceReflectionInitialization<Field.Types.Kind>();
ForceReflectionInitialization<Value.KindOneofCase>();
}
private readonly Lazy<Dictionary<IDescriptor, DescriptorDeclaration>> declarations;
private FileDescriptor(ByteString descriptorData, FileDescriptorProto proto, IEnumerable<FileDescriptor> dependencies, DescriptorPool pool, bool allowUnknownDependencies, GeneratedClrTypeInfo generatedCodeInfo)
{
SerializedData = descriptorData;
DescriptorPool = pool;
Proto = proto;
Dependencies = new ReadOnlyCollection<FileDescriptor>(dependencies.ToList());
PublicDependencies = DeterminePublicDependencies(this, proto, dependencies, allowUnknownDependencies);
pool.AddPackage(Package, this);
MessageTypes = DescriptorUtil.ConvertAndMakeReadOnly(proto.MessageType,
(message, index) =>
new MessageDescriptor(message, this, null, index, generatedCodeInfo?.NestedTypes[index]));
EnumTypes = DescriptorUtil.ConvertAndMakeReadOnly(proto.EnumType,
(enumType, index) =>
new EnumDescriptor(enumType, this, null, index, generatedCodeInfo?.NestedEnums[index]));
Services = DescriptorUtil.ConvertAndMakeReadOnly(proto.Service,
(service, index) =>
new ServiceDescriptor(service, this, index));
Extensions = new ExtensionCollection(this, generatedCodeInfo?.Extensions);
declarations = new Lazy<Dictionary<IDescriptor, DescriptorDeclaration>>(CreateDeclarationMap, LazyThreadSafetyMode.ExecutionAndPublication);
if (!proto.HasSyntax || proto.Syntax == "proto2")
{
Syntax = Syntax.Proto2;
}
else if (proto.Syntax == "proto3")
{
Syntax = Syntax.Proto3;
}
else
{
Syntax = Syntax.Unknown;
}
}
private Dictionary<IDescriptor, DescriptorDeclaration> CreateDeclarationMap()
{
var dictionary = new Dictionary<IDescriptor, DescriptorDeclaration>();
foreach (var location in Proto.SourceCodeInfo?.Location ?? Enumerable.Empty<Location>())
{
var descriptor = FindDescriptorForPath(location.Path);
if (descriptor != null)
{
dictionary[descriptor] = DescriptorDeclaration.FromProto(descriptor, location);
}
}
return dictionary;
}
private IDescriptor FindDescriptorForPath(IList<int> path)
{
// All complete declarations have an even, non-empty path length
// (There can be an empty path for a descriptor declaration, but that can't have any comments,
// so we currently ignore it.)
if (path.Count == 0 || (path.Count & 1) != 0)
{
return null;
}
IReadOnlyList<DescriptorBase> topLevelList = GetNestedDescriptorListForField(path[0]);
DescriptorBase current = GetDescriptorFromList(topLevelList, path[1]);
for (int i = 2; current != null && i < path.Count; i += 2)
{
var list = current.GetNestedDescriptorListForField(path[i]);
current = GetDescriptorFromList(list, path[i + 1]);
}
return current;
}
private DescriptorBase GetDescriptorFromList(IReadOnlyList<DescriptorBase> list, int index)
{
// This is fine: it may be a newer version of protobuf than we understand, with a new descriptor
// field.
if (list == null)
{
return null;
}
// We *could* return null to silently continue, but this is basically data corruption.
if (index < 0 || index >= list.Count)
{
// We don't have much extra information to give at this point unfortunately. If this becomes a problem,
// we can pass in the complete path and report that and the file name.
throw new InvalidProtocolBufferException($"Invalid descriptor location path: index out of range");
}
return list[index];
}
private IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber)
{
switch (fieldNumber)
{
case FileDescriptorProto.ServiceFieldNumber:
return (IReadOnlyList<DescriptorBase>) Services;
case FileDescriptorProto.MessageTypeFieldNumber:
return (IReadOnlyList<DescriptorBase>) MessageTypes;
case FileDescriptorProto.EnumTypeFieldNumber:
return (IReadOnlyList<DescriptorBase>) EnumTypes;
default:
return null;
}
}
internal DescriptorDeclaration GetDeclaration(IDescriptor descriptor)
{
DescriptorDeclaration declaration;
declarations.Value.TryGetValue(descriptor, out declaration);
return declaration;
}
/// <summary>
/// Computes the full name of a descriptor within this file, with an optional parent message.
/// </summary>
internal string ComputeFullName(MessageDescriptor parent, string name)
{
if (parent != null)
{
return parent.FullName + "." + name;
}
if (Package.Length > 0)
{
return Package + "." + name;
}
return name;
}
/// <summary>
/// Extracts public dependencies from direct dependencies. This is a static method despite its
/// first parameter, as the value we're in the middle of constructing is only used for exceptions.
/// </summary>
private static IList<FileDescriptor> DeterminePublicDependencies(FileDescriptor @this, FileDescriptorProto proto, IEnumerable<FileDescriptor> dependencies, bool allowUnknownDependencies)
{
var nameToFileMap = dependencies.ToDictionary(file => file.Name);
var publicDependencies = new List<FileDescriptor>();
for (int i = 0; i < proto.PublicDependency.Count; i++)
{
int index = proto.PublicDependency[i];
if (index < 0 || index >= proto.Dependency.Count)
{
throw new DescriptorValidationException(@this, "Invalid public dependency index.");
}
string name = proto.Dependency[index];
FileDescriptor file;
if (!nameToFileMap.TryGetValue(name, out file))
{
if (!allowUnknownDependencies)
{
throw new DescriptorValidationException(@this, "Invalid public dependency: " + name);
}
// Ignore unknown dependencies.
}
else
{
publicDependencies.Add(file);
}
}
return new ReadOnlyCollection<FileDescriptor>(publicDependencies);
}
/// <value>
/// The descriptor in its protocol message representation.
/// </value>
internal FileDescriptorProto Proto { get; }
/// <summary>
/// The syntax of the file
/// </summary>
public Syntax Syntax { get; }
/// <value>
/// The file name.
/// </value>
public string Name => Proto.Name;
/// <summary>
/// The package as declared in the .proto file. This may or may not
/// be equivalent to the .NET namespace of the generated classes.
/// </summary>
public string Package => Proto.Package;
/// <value>
/// Unmodifiable list of top-level message types declared in this file.
/// </value>
public IList<MessageDescriptor> MessageTypes { get; }
/// <value>
/// Unmodifiable list of top-level enum types declared in this file.
/// </value>
public IList<EnumDescriptor> EnumTypes { get; }
/// <value>
/// Unmodifiable list of top-level services declared in this file.
/// </value>
public IList<ServiceDescriptor> Services { get; }
/// <summary>
/// Unmodifiable list of top-level extensions declared in this file.
/// Note that some extensions may be incomplete (FieldDescriptor.Extension may be null)
/// if this descriptor was generated using a version of protoc that did not fully
/// support extensions in C#.
/// </summary>
public ExtensionCollection Extensions { get; }
/// <value>
/// Unmodifiable list of this file's dependencies (imports).
/// </value>
public IList<FileDescriptor> Dependencies { get; }
/// <value>
/// Unmodifiable list of this file's public dependencies (public imports).
/// </value>
public IList<FileDescriptor> PublicDependencies { get; }
/// <value>
/// The original serialized binary form of this descriptor.
/// </value>
public ByteString SerializedData { get; }
/// <value>
/// Implementation of IDescriptor.FullName - just returns the same as Name.
/// </value>
string IDescriptor.FullName => Name;
/// <value>
/// Implementation of IDescriptor.File - just returns this descriptor.
/// </value>
FileDescriptor IDescriptor.File => this;
/// <value>
/// Pool containing symbol descriptors.
/// </value>
internal DescriptorPool DescriptorPool { get; }
/// <summary>
/// Finds a type (message, enum, service or extension) in the file by name. Does not find nested types.
/// </summary>
/// <param name="name">The unqualified type name to look for.</param>
/// <typeparam name="T">The type of descriptor to look for</typeparam>
/// <returns>The type's descriptor, or null if not found.</returns>
public T FindTypeByName<T>(String name)
where T : class, IDescriptor
{
// Don't allow looking up nested types. This will make optimization
// easier later.
if (name.IndexOf('.') != -1)
{
return null;
}
if (Package.Length > 0)
{
name = Package + "." + name;
}
T result = DescriptorPool.FindSymbol<T>(name);
if (result != null && result.File == this)
{
return result;
}
return null;
}
/// <summary>
/// Builds a FileDescriptor from its protocol buffer representation.
/// </summary>
/// <param name="descriptorData">The original serialized descriptor data.
/// We have only limited proto2 support, so serializing FileDescriptorProto
/// would not necessarily give us this.</param>
/// <param name="proto">The protocol message form of the FileDescriptor.</param>
/// <param name="dependencies">FileDescriptors corresponding to all of the
/// file's dependencies, in the exact order listed in the .proto file. May be null,
/// in which case it is treated as an empty array.</param>
/// <param name="allowUnknownDependencies">Whether unknown dependencies are ignored (true) or cause an exception to be thrown (false).</param>
/// <param name="generatedCodeInfo">Details about generated code, for the purposes of reflection.</param>
/// <exception cref="DescriptorValidationException">If <paramref name="proto"/> is not
/// a valid descriptor. This can occur for a number of reasons, such as a field
/// having an undefined type or because two messages were defined with the same name.</exception>
private static FileDescriptor BuildFrom(ByteString descriptorData, FileDescriptorProto proto, FileDescriptor[] dependencies, bool allowUnknownDependencies, GeneratedClrTypeInfo generatedCodeInfo)
{
// Building descriptors involves two steps: translating and linking.
// In the translation step (implemented by FileDescriptor's
// constructor), we build an object tree mirroring the
// FileDescriptorProto's tree and put all of the descriptors into the
// DescriptorPool's lookup tables. In the linking step, we look up all
// type references in the DescriptorPool, so that, for example, a
// FieldDescriptor for an embedded message contains a pointer directly
// to the Descriptor for that message's type. We also detect undefined
// types in the linking step.
if (dependencies == null)
{
dependencies = new FileDescriptor[0];
}
DescriptorPool pool = new DescriptorPool(dependencies);
FileDescriptor result = new FileDescriptor(descriptorData, proto, dependencies, pool, allowUnknownDependencies, generatedCodeInfo);
// Validate that the dependencies we've been passed (as FileDescriptors) are actually the ones we
// need.
if (dependencies.Length != proto.Dependency.Count)
{
throw new DescriptorValidationException(
result,
"Dependencies passed to FileDescriptor.BuildFrom() don't match " +
"those listed in the FileDescriptorProto.");
}
result.CrossLink();
return result;
}
private void CrossLink()
{
foreach (MessageDescriptor message in MessageTypes)
{
message.CrossLink();
}
foreach (ServiceDescriptor service in Services)
{
service.CrossLink();
}
Extensions.CrossLink();
}
/// <summary>
/// Creates a descriptor for generated code.
/// </summary>
/// <remarks>
/// This method is only designed to be used by the results of generating code with protoc,
/// which creates the appropriate dependencies etc. It has to be public because the generated
/// code is "external", but should not be called directly by end users.
/// </remarks>
public static FileDescriptor FromGeneratedCode(
byte[] descriptorData,
FileDescriptor[] dependencies,
GeneratedClrTypeInfo generatedCodeInfo)
{
ExtensionRegistry registry = new ExtensionRegistry();
registry.AddRange(GetAllExtensions(dependencies, generatedCodeInfo));
FileDescriptorProto proto;
try
{
proto = FileDescriptorProto.Parser.WithExtensionRegistry(registry).ParseFrom(descriptorData);
}
catch (InvalidProtocolBufferException e)
{
throw new ArgumentException("Failed to parse protocol buffer descriptor for generated code.", e);
}
try
{
// When building descriptors for generated code, we allow unknown
// dependencies by default.
return BuildFrom(ByteString.CopyFrom(descriptorData), proto, dependencies, true, generatedCodeInfo);
}
catch (DescriptorValidationException e)
{
throw new ArgumentException($"Invalid embedded descriptor for \"{proto.Name}\".", e);
}
}
private static IEnumerable<Extension> GetAllExtensions(FileDescriptor[] dependencies, GeneratedClrTypeInfo generatedInfo)
{
return dependencies.SelectMany(GetAllDependedExtensions).Distinct(ExtensionRegistry.ExtensionComparer.Instance).Concat(GetAllGeneratedExtensions(generatedInfo));
}
private static IEnumerable<Extension> GetAllGeneratedExtensions(GeneratedClrTypeInfo generated)
{
return generated.Extensions.Concat(generated.NestedTypes.Where(t => t != null).SelectMany(GetAllGeneratedExtensions));
}
private static IEnumerable<Extension> GetAllDependedExtensions(FileDescriptor descriptor)
{
return descriptor.Extensions.UnorderedExtensions
.Select(s => s.Extension)
.Where(e => e != null)
.Concat(descriptor.Dependencies.Concat(descriptor.PublicDependencies).SelectMany(GetAllDependedExtensions))
.Concat(descriptor.MessageTypes.SelectMany(GetAllDependedExtensionsFromMessage));
}
private static IEnumerable<Extension> GetAllDependedExtensionsFromMessage(MessageDescriptor descriptor)
{
return descriptor.Extensions.UnorderedExtensions
.Select(s => s.Extension)
.Where(e => e != null)
.Concat(descriptor.NestedTypes.SelectMany(GetAllDependedExtensionsFromMessage));
}
/// <summary>
/// Converts the given descriptor binary data into FileDescriptor objects.
/// Note: reflection using the returned FileDescriptors is not currently supported.
/// </summary>
/// <param name="descriptorData">The binary file descriptor proto data. Must not be null, and any
/// dependencies must come before the descriptor which depends on them. (If A depends on B, and B
/// depends on C, then the descriptors must be presented in the order C, B, A.) This is compatible
/// with the order in which protoc provides descriptors to plugins.</param>
/// <returns>The file descriptors corresponding to <paramref name="descriptorData"/>.</returns>
public static IReadOnlyList<FileDescriptor> BuildFromByteStrings(IEnumerable<ByteString> descriptorData)
{
ProtoPreconditions.CheckNotNull(descriptorData, nameof(descriptorData));
// TODO: See if we can build a single DescriptorPool instead of building lots of them.
// This will all behave correctly, but it's less efficient than we'd like.
var descriptors = new List<FileDescriptor>();
var descriptorsByName = new Dictionary<string, FileDescriptor>();
foreach (var data in descriptorData)
{
var proto = FileDescriptorProto.Parser.ParseFrom(data);
var dependencies = new List<FileDescriptor>();
foreach (var dependencyName in proto.Dependency)
{
FileDescriptor dependency;
if (!descriptorsByName.TryGetValue(dependencyName, out dependency))
{
throw new ArgumentException($"Dependency missing: {dependencyName}");
}
dependencies.Add(dependency);
}
var pool = new DescriptorPool(dependencies);
FileDescriptor descriptor = new FileDescriptor(
data, proto, dependencies, pool,
allowUnknownDependencies: false, generatedCodeInfo: null);
descriptor.CrossLink();
descriptors.Add(descriptor);
if (descriptorsByName.ContainsKey(descriptor.Name))
{
throw new ArgumentException($"Duplicate descriptor name: {descriptor.Name}");
}
descriptorsByName.Add(descriptor.Name, descriptor);
}
return new ReadOnlyCollection<FileDescriptor>(descriptors);
}
/// <summary>
/// Returns a <see cref="System.String" /> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String" /> that represents this instance.
/// </returns>
public override string ToString()
{
return $"FileDescriptor for {Name}";
}
/// <summary>
/// Returns the file descriptor for descriptor.proto.
/// </summary>
/// <remarks>
/// This is used for protos which take a direct dependency on <c>descriptor.proto</c>, typically for
/// annotations. While <c>descriptor.proto</c> is a proto2 file, it is built into the Google.Protobuf
/// runtime for reflection purposes. The messages are internal to the runtime as they would require
/// proto2 semantics for full support, but the file descriptor is available via this property. The
/// C# codegen in protoc automatically uses this property when it detects a dependency on <c>descriptor.proto</c>.
/// </remarks>
/// <value>
/// The file descriptor for <c>descriptor.proto</c>.
/// </value>
public static FileDescriptor DescriptorProtoFileDescriptor { get { return DescriptorReflection.Descriptor; } }
/// <summary>
/// The (possibly empty) set of custom options for this file.
/// </summary>
[Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")]
public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
/// <summary>
/// The <c>FileOptions</c>, defined in <c>descriptor.proto</c>.
/// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
/// Custom options can be retrieved as extensions of the returned message.
/// NOTE: A defensive copy is created each time this property is retrieved.
/// </summary>
public FileOptions GetOptions() => Proto.Options?.Clone();
/// <summary>
/// Gets a single value file option for this descriptor
/// </summary>
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
public T GetOption<T>(Extension<FileOptions, T> extension)
{
var value = Proto.Options.GetExtension(extension);
return value is IDeepCloneable<T> ? (value as IDeepCloneable<T>).Clone() : value;
}
/// <summary>
/// Gets a repeated value file option for this descriptor
/// </summary>
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
public RepeatedField<T> GetOption<T>(RepeatedExtension<FileOptions, T> extension)
{
return Proto.Options.GetExtension(extension).Clone();
}
/// <summary>
/// Performs initialization for the given generic type argument.
/// </summary>
/// <remarks>
/// This method is present for the sake of AOT compilers. It allows code (whether handwritten or generated)
/// to make calls into the reflection machinery of this library to express an intention to use that type
/// reflectively (e.g. for JSON parsing and formatting). The call itself does almost nothing, but AOT compilers
/// attempting to determine which generic type arguments need to be handled will spot the code path and act
/// accordingly.
/// </remarks>
/// <typeparam name="T">The type to force initialization for.</typeparam>
public static void ForceReflectionInitialization<T>() => ReflectionUtil.ForceInitialize<T>();
}
}

View File

@ -0,0 +1,128 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
namespace LC.Google.Protobuf.Reflection
{
/// <summary>
/// Extra information provided by generated code when initializing a message or file descriptor.
/// These are constructed as required, and are not long-lived. Hand-written code should
/// never need to use this type.
/// </summary>
public sealed class GeneratedClrTypeInfo
{
private static readonly string[] EmptyNames = new string[0];
private static readonly GeneratedClrTypeInfo[] EmptyCodeInfo = new GeneratedClrTypeInfo[0];
private static readonly Extension[] EmptyExtensions = new Extension[0];
/// <summary>
/// Irrelevant for file descriptors; the CLR type for the message for message descriptors.
/// </summary>
public Type ClrType { get; private set; }
/// <summary>
/// Irrelevant for file descriptors; the parser for message descriptors.
/// </summary>
public MessageParser Parser { get; }
/// <summary>
/// Irrelevant for file descriptors; the CLR property names (in message descriptor field order)
/// for fields in the message for message descriptors.
/// </summary>
public string[] PropertyNames { get; }
/// <summary>
/// The extensions defined within this file/message descriptor
/// </summary>
public Extension[] Extensions { get; }
/// <summary>
/// Irrelevant for file descriptors; the CLR property "base" names (in message descriptor oneof order)
/// for oneofs in the message for message descriptors. It is expected that for a oneof name of "Foo",
/// there will be a "FooCase" property and a "ClearFoo" method.
/// </summary>
public string[] OneofNames { get; }
/// <summary>
/// The reflection information for types within this file/message descriptor. Elements may be null
/// if there is no corresponding generated type, e.g. for map entry types.
/// </summary>
public GeneratedClrTypeInfo[] NestedTypes { get; }
/// <summary>
/// The CLR types for enums within this file/message descriptor.
/// </summary>
public Type[] NestedEnums { get; }
/// <summary>
/// Creates a GeneratedClrTypeInfo for a message descriptor, with nested types, nested enums, the CLR type, property names and oneof names.
/// Each array parameter may be null, to indicate a lack of values.
/// The parameter order is designed to make it feasible to format the generated code readably.
/// </summary>
public GeneratedClrTypeInfo(Type clrType, MessageParser parser, string[] propertyNames, string[] oneofNames, Type[] nestedEnums, Extension[] extensions, GeneratedClrTypeInfo[] nestedTypes)
{
NestedTypes = nestedTypes ?? EmptyCodeInfo;
NestedEnums = nestedEnums ?? ReflectionUtil.EmptyTypes;
ClrType = clrType;
Extensions = extensions ?? EmptyExtensions;
Parser = parser;
PropertyNames = propertyNames ?? EmptyNames;
OneofNames = oneofNames ?? EmptyNames;
}
/// <summary>
/// Creates a GeneratedClrTypeInfo for a message descriptor, with nested types, nested enums, the CLR type, property names and oneof names.
/// Each array parameter may be null, to indicate a lack of values.
/// The parameter order is designed to make it feasible to format the generated code readably.
/// </summary>
public GeneratedClrTypeInfo(Type clrType, MessageParser parser, string[] propertyNames, string[] oneofNames, Type[] nestedEnums, GeneratedClrTypeInfo[] nestedTypes)
: this(clrType, parser, propertyNames, oneofNames, nestedEnums, null, nestedTypes)
{
}
/// <summary>
/// Creates a GeneratedClrTypeInfo for a file descriptor, with only types, enums, and extensions.
/// </summary>
public GeneratedClrTypeInfo(Type[] nestedEnums, Extension[] extensions, GeneratedClrTypeInfo[] nestedTypes)
: this(null, null, null, null, nestedEnums, extensions, nestedTypes)
{
}
/// <summary>
/// Creates a GeneratedClrTypeInfo for a file descriptor, with only types and enums.
/// </summary>
public GeneratedClrTypeInfo(Type[] nestedEnums, GeneratedClrTypeInfo[] nestedTypes)
: this(null, null, null, null, nestedEnums, nestedTypes)
{
}
}
}

View File

@ -0,0 +1,55 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
namespace LC.Google.Protobuf.Reflection
{
/// <summary>
/// Interface implemented by all descriptor types.
/// </summary>
public interface IDescriptor
{
/// <summary>
/// Returns the name of the entity (message, field etc) being described.
/// </summary>
string Name { get; }
/// <summary>
/// Returns the fully-qualified name of the entity being described.
/// </summary>
string FullName { get; }
/// <summary>
/// Returns the descriptor for the .proto file that this entity is part of.
/// </summary>
FileDescriptor File { get; }
}
}

View File

@ -0,0 +1,77 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Collections;
namespace LC.Google.Protobuf.Reflection
{
/// <summary>
/// Allows fields to be reflectively accessed.
/// </summary>
public interface IFieldAccessor
{
/// <summary>
/// Returns the descriptor associated with this field.
/// </summary>
FieldDescriptor Descriptor { get; }
/// <summary>
/// Clears the field in the specified message. (For repeated fields,
/// this clears the list.)
/// </summary>
void Clear(IMessage message);
/// <summary>
/// Fetches the field value. For repeated values, this will be an
/// <see cref="IList"/> implementation. For map values, this will be an
/// <see cref="IDictionary"/> implementation.
/// </summary>
object GetValue(IMessage message);
/// <summary>
/// Indicates whether the field in the specified message is set.
/// For proto3 fields that aren't explicitly optional, this throws an <see cref="InvalidOperationException"/>
/// </summary>
bool HasValue(IMessage message);
/// <summary>
/// Mutator for single "simple" fields only.
/// </summary>
/// <remarks>
/// Repeated fields are mutated by fetching the value and manipulating it as a list.
/// Map fields are mutated by fetching the value and manipulating it as a dictionary.
/// </remarks>
/// <exception cref="InvalidOperationException">The field is not a "simple" field.</exception>
void SetValue(IMessage message, object value);
}
}

View File

@ -0,0 +1,64 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Collections;
using System.Reflection;
namespace LC.Google.Protobuf.Reflection
{
/// <summary>
/// Accessor for map fields.
/// </summary>
internal sealed class MapFieldAccessor : FieldAccessorBase
{
internal MapFieldAccessor(PropertyInfo property, FieldDescriptor descriptor) : base(property, descriptor)
{
}
public override void Clear(IMessage message)
{
IDictionary list = (IDictionary) GetValue(message);
list.Clear();
}
public override bool HasValue(IMessage message)
{
throw new InvalidOperationException("HasValue is not implemented for map fields");
}
public override void SetValue(IMessage message, object value)
{
throw new InvalidOperationException("SetValue is not implemented for map fields");
}
}
}

View File

@ -0,0 +1,420 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
#if NET35
// Needed for ReadOnlyDictionary, which does not exist in .NET 3.5
using LC.Google.Protobuf.Collections;
#endif
namespace LC.Google.Protobuf.Reflection
{
/// <summary>
/// Describes a message type.
/// </summary>
public sealed class MessageDescriptor : DescriptorBase
{
private static readonly HashSet<string> WellKnownTypeNames = new HashSet<string>
{
"google/protobuf/any.proto",
"google/protobuf/api.proto",
"google/protobuf/duration.proto",
"google/protobuf/empty.proto",
"google/protobuf/wrappers.proto",
"google/protobuf/timestamp.proto",
"google/protobuf/field_mask.proto",
"google/protobuf/source_context.proto",
"google/protobuf/struct.proto",
"google/protobuf/type.proto",
};
private readonly IList<FieldDescriptor> fieldsInDeclarationOrder;
private readonly IList<FieldDescriptor> fieldsInNumberOrder;
private readonly IDictionary<string, FieldDescriptor> jsonFieldMap;
private Func<IMessage, bool> extensionSetIsInitialized;
internal MessageDescriptor(DescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int typeIndex, GeneratedClrTypeInfo generatedCodeInfo)
: base(file, file.ComputeFullName(parent, proto.Name), typeIndex)
{
Proto = proto;
Parser = generatedCodeInfo?.Parser;
ClrType = generatedCodeInfo?.ClrType;
ContainingType = parent;
// If generatedCodeInfo is null, we just won't generate an accessor for any fields.
Oneofs = DescriptorUtil.ConvertAndMakeReadOnly(
proto.OneofDecl,
(oneof, index) =>
new OneofDescriptor(oneof, file, this, index, generatedCodeInfo?.OneofNames[index]));
int syntheticOneofCount = 0;
foreach (var oneof in Oneofs)
{
if (oneof.IsSynthetic)
{
syntheticOneofCount++;
}
else if (syntheticOneofCount != 0)
{
throw new ArgumentException("All synthetic oneofs should come after real oneofs");
}
}
RealOneofCount = Oneofs.Count - syntheticOneofCount;
NestedTypes = DescriptorUtil.ConvertAndMakeReadOnly(
proto.NestedType,
(type, index) =>
new MessageDescriptor(type, file, this, index, generatedCodeInfo?.NestedTypes[index]));
EnumTypes = DescriptorUtil.ConvertAndMakeReadOnly(
proto.EnumType,
(type, index) =>
new EnumDescriptor(type, file, this, index, generatedCodeInfo?.NestedEnums[index]));
Extensions = new ExtensionCollection(this, generatedCodeInfo?.Extensions);
fieldsInDeclarationOrder = DescriptorUtil.ConvertAndMakeReadOnly(
proto.Field,
(field, index) =>
new FieldDescriptor(field, file, this, index, generatedCodeInfo?.PropertyNames[index], null));
fieldsInNumberOrder = new ReadOnlyCollection<FieldDescriptor>(fieldsInDeclarationOrder.OrderBy(field => field.FieldNumber).ToArray());
// TODO: Use field => field.Proto.JsonName when we're confident it's appropriate. (And then use it in the formatter, too.)
jsonFieldMap = CreateJsonFieldMap(fieldsInNumberOrder);
file.DescriptorPool.AddSymbol(this);
Fields = new FieldCollection(this);
}
private static ReadOnlyDictionary<string, FieldDescriptor> CreateJsonFieldMap(IList<FieldDescriptor> fields)
{
var map = new Dictionary<string, FieldDescriptor>();
foreach (var field in fields)
{
map[field.Name] = field;
map[field.JsonName] = field;
}
return new ReadOnlyDictionary<string, FieldDescriptor>(map);
}
/// <summary>
/// The brief name of the descriptor's target.
/// </summary>
public override string Name => Proto.Name;
internal override IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber)
{
switch (fieldNumber)
{
case DescriptorProto.FieldFieldNumber:
return (IReadOnlyList<DescriptorBase>) fieldsInDeclarationOrder;
case DescriptorProto.NestedTypeFieldNumber:
return (IReadOnlyList<DescriptorBase>) NestedTypes;
case DescriptorProto.EnumTypeFieldNumber:
return (IReadOnlyList<DescriptorBase>) EnumTypes;
default:
return null;
}
}
internal DescriptorProto Proto { get; }
internal bool IsExtensionsInitialized(IMessage message)
{
if (Proto.ExtensionRange.Count == 0)
{
return true;
}
if (extensionSetIsInitialized == null)
{
extensionSetIsInitialized = ReflectionUtil.CreateIsInitializedCaller(ClrType);
}
return extensionSetIsInitialized(message);
}
/// <summary>
/// The CLR type used to represent message instances from this descriptor.
/// </summary>
/// <remarks>
/// <para>
/// The value returned by this property will be non-null for all regular fields. However,
/// if a message containing a map field is introspected, the list of nested messages will include
/// an auto-generated nested key/value pair message for the field. This is not represented in any
/// generated type, so this property will return null in such cases.
/// </para>
/// <para>
/// For wrapper types (<see cref="Google.Protobuf.WellKnownTypes.StringValue"/> and the like), the type returned here
/// will be the generated message type, not the native type used by reflection for fields of those types. Code
/// using reflection should call <see cref="IsWrapperType"/> to determine whether a message descriptor represents
/// a wrapper type, and handle the result appropriately.
/// </para>
/// </remarks>
public Type ClrType { get; }
/// <summary>
/// A parser for this message type.
/// </summary>
/// <remarks>
/// <para>
/// As <see cref="MessageDescriptor"/> is not generic, this cannot be statically
/// typed to the relevant type, but it should produce objects of a type compatible with <see cref="ClrType"/>.
/// </para>
/// <para>
/// The value returned by this property will be non-null for all regular fields. However,
/// if a message containing a map field is introspected, the list of nested messages will include
/// an auto-generated nested key/value pair message for the field. No message parser object is created for
/// such messages, so this property will return null in such cases.
/// </para>
/// <para>
/// For wrapper types (<see cref="Google.Protobuf.WellKnownTypes.StringValue"/> and the like), the parser returned here
/// will be the generated message type, not the native type used by reflection for fields of those types. Code
/// using reflection should call <see cref="IsWrapperType"/> to determine whether a message descriptor represents
/// a wrapper type, and handle the result appropriately.
/// </para>
/// </remarks>
public MessageParser Parser { get; }
/// <summary>
/// Returns whether this message is one of the "well known types" which may have runtime/protoc support.
/// </summary>
internal bool IsWellKnownType => File.Package == "google.protobuf" && WellKnownTypeNames.Contains(File.Name);
/// <summary>
/// Returns whether this message is one of the "wrapper types" used for fields which represent primitive values
/// with the addition of presence.
/// </summary>
internal bool IsWrapperType => File.Package == "google.protobuf" && File.Name == "google/protobuf/wrappers.proto";
/// <value>
/// If this is a nested type, get the outer descriptor, otherwise null.
/// </value>
public MessageDescriptor ContainingType { get; }
/// <value>
/// A collection of fields, which can be retrieved by name or field number.
/// </value>
public FieldCollection Fields { get; }
/// <summary>
/// An unmodifiable list of extensions defined in this message's scope.
/// Note that some extensions may be incomplete (FieldDescriptor.Extension may be null)
/// if they are declared in a file generated using a version of protoc that did not fully
/// support extensions in C#.
/// </summary>
public ExtensionCollection Extensions { get; }
/// <value>
/// An unmodifiable list of this message type's nested types.
/// </value>
public IList<MessageDescriptor> NestedTypes { get; }
/// <value>
/// An unmodifiable list of this message type's enum types.
/// </value>
public IList<EnumDescriptor> EnumTypes { get; }
/// <value>
/// An unmodifiable list of the "oneof" field collections in this message type.
/// All "real" oneofs (where <see cref="OneofDescriptor.IsSynthetic"/> returns false)
/// come before synthetic ones.
/// </value>
public IList<OneofDescriptor> Oneofs { get; }
/// <summary>
/// The number of real "oneof" descriptors in this message type. Every element in <see cref="Oneofs"/>
/// with an index less than this will have a <see cref="OneofDescriptor.IsSynthetic"/> property value
/// of <c>false</c>; every element with an index greater than or equal to this will have a
/// <see cref="OneofDescriptor.IsSynthetic"/> property value of <c>true</c>.
/// </summary>
public int RealOneofCount { get; }
/// <summary>
/// Finds a field by field name.
/// </summary>
/// <param name="name">The unqualified name of the field (e.g. "foo").</param>
/// <returns>The field's descriptor, or null if not found.</returns>
public FieldDescriptor FindFieldByName(String name) => File.DescriptorPool.FindSymbol<FieldDescriptor>(FullName + "." + name);
/// <summary>
/// Finds a field by field number.
/// </summary>
/// <param name="number">The field number within this message type.</param>
/// <returns>The field's descriptor, or null if not found.</returns>
public FieldDescriptor FindFieldByNumber(int number) => File.DescriptorPool.FindFieldByNumber(this, number);
/// <summary>
/// Finds a nested descriptor by name. The is valid for fields, nested
/// message types, oneofs and enums.
/// </summary>
/// <param name="name">The unqualified name of the descriptor, e.g. "Foo"</param>
/// <returns>The descriptor, or null if not found.</returns>
public T FindDescriptor<T>(string name) where T : class, IDescriptor =>
File.DescriptorPool.FindSymbol<T>(FullName + "." + name);
/// <summary>
/// The (possibly empty) set of custom options for this message.
/// </summary>
[Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")]
public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
/// <summary>
/// The <c>MessageOptions</c>, defined in <c>descriptor.proto</c>.
/// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
/// Custom options can be retrieved as extensions of the returned message.
/// NOTE: A defensive copy is created each time this property is retrieved.
/// </summary>
public MessageOptions GetOptions() => Proto.Options?.Clone();
/// <summary>
/// Gets a single value message option for this descriptor
/// </summary>
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
public T GetOption<T>(Extension<MessageOptions, T> extension)
{
var value = Proto.Options.GetExtension(extension);
return value is IDeepCloneable<T> ? (value as IDeepCloneable<T>).Clone() : value;
}
/// <summary>
/// Gets a repeated value message option for this descriptor
/// </summary>
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
public Collections.RepeatedField<T> GetOption<T>(RepeatedExtension<MessageOptions, T> extension)
{
return Proto.Options.GetExtension(extension).Clone();
}
/// <summary>
/// Looks up and cross-links all fields and nested types.
/// </summary>
internal void CrossLink()
{
foreach (MessageDescriptor message in NestedTypes)
{
message.CrossLink();
}
foreach (FieldDescriptor field in fieldsInDeclarationOrder)
{
field.CrossLink();
}
foreach (OneofDescriptor oneof in Oneofs)
{
oneof.CrossLink();
}
Extensions.CrossLink();
}
/// <summary>
/// A collection to simplify retrieving the field accessor for a particular field.
/// </summary>
public sealed class FieldCollection
{
private readonly MessageDescriptor messageDescriptor;
internal FieldCollection(MessageDescriptor messageDescriptor)
{
this.messageDescriptor = messageDescriptor;
}
/// <value>
/// Returns the fields in the message as an immutable list, in the order in which they
/// are declared in the source .proto file.
/// </value>
public IList<FieldDescriptor> InDeclarationOrder() => messageDescriptor.fieldsInDeclarationOrder;
/// <value>
/// Returns the fields in the message as an immutable list, in ascending field number
/// order. Field numbers need not be contiguous, so there is no direct mapping from the
/// index in the list to the field number; to retrieve a field by field number, it is better
/// to use the <see cref="FieldCollection"/> indexer.
/// </value>
public IList<FieldDescriptor> InFieldNumberOrder() => messageDescriptor.fieldsInNumberOrder;
// TODO: consider making this public in the future. (Being conservative for now...)
/// <value>
/// Returns a read-only dictionary mapping the field names in this message as they're available
/// in the JSON representation to the field descriptors. For example, a field <c>foo_bar</c>
/// in the message would result two entries, one with a key <c>fooBar</c> and one with a key
/// <c>foo_bar</c>, both referring to the same field.
/// </value>
internal IDictionary<string, FieldDescriptor> ByJsonName() => messageDescriptor.jsonFieldMap;
/// <summary>
/// Retrieves the descriptor for the field with the given number.
/// </summary>
/// <param name="number">Number of the field to retrieve the descriptor for</param>
/// <returns>The accessor for the given field</returns>
/// <exception cref="KeyNotFoundException">The message descriptor does not contain a field
/// with the given number</exception>
public FieldDescriptor this[int number]
{
get
{
var fieldDescriptor = messageDescriptor.FindFieldByNumber(number);
if (fieldDescriptor == null)
{
throw new KeyNotFoundException("No such field number");
}
return fieldDescriptor;
}
}
/// <summary>
/// Retrieves the descriptor for the field with the given name.
/// </summary>
/// <param name="name">Name of the field to retrieve the descriptor for</param>
/// <returns>The descriptor for the given field</returns>
/// <exception cref="KeyNotFoundException">The message descriptor does not contain a field
/// with the given name</exception>
public FieldDescriptor this[string name]
{
get
{
var fieldDescriptor = messageDescriptor.FindFieldByName(name);
if (fieldDescriptor == null)
{
throw new KeyNotFoundException("No such field name");
}
return fieldDescriptor;
}
}
}
}
}

View File

@ -0,0 +1,140 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using LC.Google.Protobuf.Collections;
using System;
namespace LC.Google.Protobuf.Reflection
{
/// <summary>
/// Describes a single method in a service.
/// </summary>
public sealed class MethodDescriptor : DescriptorBase
{
private readonly MethodDescriptorProto proto;
private readonly ServiceDescriptor service;
private MessageDescriptor inputType;
private MessageDescriptor outputType;
/// <value>
/// The service this method belongs to.
/// </value>
public ServiceDescriptor Service { get { return service; } }
/// <value>
/// The method's input type.
/// </value>
public MessageDescriptor InputType { get { return inputType; } }
/// <value>
/// The method's input type.
/// </value>
public MessageDescriptor OutputType { get { return outputType; } }
/// <value>
/// Indicates if client streams multiple requests.
/// </value>
public bool IsClientStreaming { get { return proto.ClientStreaming; } }
/// <value>
/// Indicates if server streams multiple responses.
/// </value>
public bool IsServerStreaming { get { return proto.ServerStreaming; } }
/// <summary>
/// The (possibly empty) set of custom options for this method.
/// </summary>
[Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")]
public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
/// <summary>
/// The <c>MethodOptions</c>, defined in <c>descriptor.proto</c>.
/// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
/// Custom options can be retrieved as extensions of the returned message.
/// NOTE: A defensive copy is created each time this property is retrieved.
/// </summary>
public MethodOptions GetOptions() => Proto.Options?.Clone();
/// <summary>
/// Gets a single value method option for this descriptor
/// </summary>
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
public T GetOption<T>(Extension<MethodOptions, T> extension)
{
var value = Proto.Options.GetExtension(extension);
return value is IDeepCloneable<T> ? (value as IDeepCloneable<T>).Clone() : value;
}
/// <summary>
/// Gets a repeated value method option for this descriptor
/// </summary>
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
public RepeatedField<T> GetOption<T>(RepeatedExtension<MethodOptions, T> extension)
{
return Proto.Options.GetExtension(extension).Clone();
}
internal MethodDescriptor(MethodDescriptorProto proto, FileDescriptor file,
ServiceDescriptor parent, int index)
: base(file, parent.FullName + "." + proto.Name, index)
{
this.proto = proto;
service = parent;
file.DescriptorPool.AddSymbol(this);
}
internal MethodDescriptorProto Proto { get { return proto; } }
/// <summary>
/// The brief name of the descriptor's target.
/// </summary>
public override string Name { get { return proto.Name; } }
internal void CrossLink()
{
IDescriptor lookup = File.DescriptorPool.LookupSymbol(Proto.InputType, this);
if (!(lookup is MessageDescriptor))
{
throw new DescriptorValidationException(this, "\"" + Proto.InputType + "\" is not a message type.");
}
inputType = (MessageDescriptor) lookup;
lookup = File.DescriptorPool.LookupSymbol(Proto.OutputType, this);
if (!(lookup is MessageDescriptor))
{
throw new DescriptorValidationException(this, "\"" + Proto.OutputType + "\" is not a message type.");
}
outputType = (MessageDescriptor) lookup;
}
}
}

View File

@ -0,0 +1,97 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Reflection;
using LC.Google.Protobuf.Compatibility;
namespace LC.Google.Protobuf.Reflection
{
/// <summary>
/// Reflection access for a oneof, allowing clear and "get case" actions.
/// </summary>
public sealed class OneofAccessor
{
private readonly Func<IMessage, int> caseDelegate;
private readonly Action<IMessage> clearDelegate;
private OneofAccessor(OneofDescriptor descriptor, Func<IMessage, int> caseDelegate, Action<IMessage> clearDelegate)
{
Descriptor = descriptor;
this.caseDelegate = caseDelegate;
this.clearDelegate = clearDelegate;
}
internal static OneofAccessor ForRegularOneof(
OneofDescriptor descriptor,
PropertyInfo caseProperty,
MethodInfo clearMethod) =>
new OneofAccessor(
descriptor,
ReflectionUtil.CreateFuncIMessageInt32(caseProperty.GetGetMethod()),
ReflectionUtil.CreateActionIMessage(clearMethod));
internal static OneofAccessor ForSyntheticOneof(OneofDescriptor descriptor)
{
// Note: descriptor.Fields will be null when this method is called, because we haven't
// cross-linked yet. But by the time the delegates are called by user code, all will be
// well. (That's why we capture the descriptor itself rather than a field.)
return new OneofAccessor(descriptor,
message => descriptor.Fields[0].Accessor.HasValue(message) ? descriptor.Fields[0].FieldNumber : 0,
message => descriptor.Fields[0].Accessor.Clear(message));
}
/// <summary>
/// Gets the descriptor for this oneof.
/// </summary>
/// <value>
/// The descriptor of the oneof.
/// </value>
public OneofDescriptor Descriptor { get; }
/// <summary>
/// Clears the oneof in the specified message.
/// </summary>
public void Clear(IMessage message) => clearDelegate(message);
/// <summary>
/// Indicates which field in the oneof is set for specified message
/// </summary>
public FieldDescriptor GetCaseFieldDescriptor(IMessage message)
{
int fieldNumber = caseDelegate(message);
return fieldNumber > 0
? Descriptor.ContainingType.FindFieldByNumber(fieldNumber)
: null;
}
}
}

View File

@ -0,0 +1,195 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using LC.Google.Protobuf.Collections;
using LC.Google.Protobuf.Compatibility;
namespace LC.Google.Protobuf.Reflection
{
/// <summary>
/// Describes a "oneof" field collection in a message type: a set of
/// fields of which at most one can be set in any particular message.
/// </summary>
public sealed class OneofDescriptor : DescriptorBase
{
private readonly OneofDescriptorProto proto;
private MessageDescriptor containingType;
private IList<FieldDescriptor> fields;
private readonly OneofAccessor accessor;
internal OneofDescriptor(OneofDescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int index, string clrName)
: base(file, file.ComputeFullName(parent, proto.Name), index)
{
this.proto = proto;
containingType = parent;
file.DescriptorPool.AddSymbol(this);
// It's useful to determine whether or not this is a synthetic oneof before cross-linking. That means
// diving into the proto directly rather than using FieldDescriptor, but that's okay.
var firstFieldInOneof = parent.Proto.Field.FirstOrDefault(fieldProto => fieldProto.HasOneofIndex && fieldProto.OneofIndex == index);
IsSynthetic = firstFieldInOneof?.Proto3Optional ?? false;
accessor = CreateAccessor(clrName);
}
/// <summary>
/// The brief name of the descriptor's target.
/// </summary>
public override string Name { get { return proto.Name; } }
/// <summary>
/// Gets the message type containing this oneof.
/// </summary>
/// <value>
/// The message type containing this oneof.
/// </value>
public MessageDescriptor ContainingType
{
get { return containingType; }
}
/// <summary>
/// Gets the fields within this oneof, in declaration order.
/// </summary>
/// <value>
/// The fields within this oneof, in declaration order.
/// </value>
public IList<FieldDescriptor> Fields { get { return fields; } }
/// <summary>
/// Returns <c>true</c> if this oneof is a synthetic oneof containing a proto3 optional field;
/// <c>false</c> otherwise.
/// </summary>
public bool IsSynthetic { get; }
/// <summary>
/// Gets an accessor for reflective access to the values associated with the oneof
/// in a particular message.
/// </summary>
/// <remarks>
/// <para>
/// In descriptors for generated code, the value returned by this property will always be non-null.
/// </para>
/// <para>
/// In dynamically loaded descriptors, the value returned by this property will current be null;
/// if and when dynamic messages are supported, it will return a suitable accessor to work with
/// them.
/// </para>
/// </remarks>
/// <value>
/// The accessor used for reflective access.
/// </value>
public OneofAccessor Accessor { get { return accessor; } }
/// <summary>
/// The (possibly empty) set of custom options for this oneof.
/// </summary>
[Obsolete("CustomOptions are obsolete. Use the GetOptions method.")]
public CustomOptions CustomOptions => new CustomOptions(proto.Options?._extensions?.ValuesByNumber);
/// <summary>
/// The <c>OneofOptions</c>, defined in <c>descriptor.proto</c>.
/// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
/// Custom options can be retrieved as extensions of the returned message.
/// NOTE: A defensive copy is created each time this property is retrieved.
/// </summary>
public OneofOptions GetOptions() => proto.Options?.Clone();
/// <summary>
/// Gets a single value oneof option for this descriptor
/// </summary>
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
public T GetOption<T>(Extension<OneofOptions, T> extension)
{
var value = proto.Options.GetExtension(extension);
return value is IDeepCloneable<T> ? (value as IDeepCloneable<T>).Clone() : value;
}
/// <summary>
/// Gets a repeated value oneof option for this descriptor
/// </summary>
[Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
public RepeatedField<T> GetOption<T>(RepeatedExtension<OneofOptions, T> extension)
{
return proto.Options.GetExtension(extension).Clone();
}
internal void CrossLink()
{
List<FieldDescriptor> fieldCollection = new List<FieldDescriptor>();
foreach (var field in ContainingType.Fields.InDeclarationOrder())
{
if (field.ContainingOneof == this)
{
fieldCollection.Add(field);
}
}
fields = new ReadOnlyCollection<FieldDescriptor>(fieldCollection);
}
private OneofAccessor CreateAccessor(string clrName)
{
// We won't have a CLR name if this is from a dynamically-loaded FileDescriptor.
// TODO: Support dynamic messages.
if (clrName == null)
{
return null;
}
if (IsSynthetic)
{
return OneofAccessor.ForSyntheticOneof(this);
}
else
{
var caseProperty = containingType.ClrType.GetProperty(clrName + "Case");
if (caseProperty == null)
{
throw new DescriptorValidationException(this, $"Property {clrName}Case not found in {containingType.ClrType}");
}
if (!caseProperty.CanRead)
{
throw new ArgumentException($"Cannot read from property {clrName}Case in {containingType.ClrType}");
}
var clearMethod = containingType.ClrType.GetMethod("Clear" + clrName);
if (clearMethod == null)
{
throw new DescriptorValidationException(this, $"Method Clear{clrName} not found in {containingType.ClrType}");
}
return OneofAccessor.ForRegularOneof(this, caseProperty, clearMethod);
}
}
}
}

View File

@ -0,0 +1,65 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
namespace LC.Google.Protobuf.Reflection
{
/// <summary>
/// Specifies the original name (in the .proto file) of a named element,
/// such as an enum value.
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class OriginalNameAttribute : Attribute
{
/// <summary>
/// The name of the element in the .proto file.
/// </summary>
public string Name { get; set; }
/// <summary>
/// If the name is preferred in the .proto file.
/// </summary>
public bool PreferredAlias { get; set; }
/// <summary>
/// Constructs a new attribute instance for the given name.
/// </summary>
/// <param name="name">The name of the element in the .proto file.</param>
public OriginalNameAttribute(string name)
{
Name = ProtoPreconditions.CheckNotNull(name, nameof(name));
PreferredAlias = true;
}
}
}

View File

@ -0,0 +1,68 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
namespace LC.Google.Protobuf.Reflection
{
/// <summary>
/// Represents a package in the symbol table. We use PackageDescriptors
/// just as placeholders so that someone cannot define, say, a message type
/// that has the same name as an existing package.
/// </summary>
internal sealed class PackageDescriptor : IDescriptor
{
private readonly string name;
private readonly string fullName;
private readonly FileDescriptor file;
internal PackageDescriptor(string name, string fullName, FileDescriptor file)
{
this.file = file;
this.fullName = fullName;
this.name = name;
}
public string Name
{
get { return name; }
}
public string FullName
{
get { return fullName; }
}
public FileDescriptor File
{
get { return file; }
}
}
}

Some files were not shown because too many files have changed in this diff Show More