tls/http statistics
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
DataHoarder 2022-06-10 11:22:11 +02:00
parent 5f53981562
commit 85e6a21717
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
3 changed files with 142 additions and 33 deletions

View file

@ -2,6 +2,7 @@ package main
import (
"bytes"
"crypto/tls"
"database/sql"
"encoding/base64"
"encoding/hex"
@ -50,7 +51,14 @@ type statistics struct {
Served servedStatistics `json:"served"`
ContentCache contentCacheStatistics `json:"content_cache"`
SignatureCache signatureCacheStatistics `json:"signature_cache"`
HttpCode httpCodeStatistics `json:"http_code"`
Http struct {
Code httpCodeStatistics `json:"code"`
Protocol httpProtocolStatistics `json:"protocol"`
}
Tls struct {
Version tlsVersionStatistics `json:"version"`
Cipher tlsCipherStatistics `json:"cipher"`
}
}
type servedStatistics struct {
@ -72,6 +80,7 @@ type signatureCacheStatistics struct {
type httpCodeStatistics struct {
Code200 uint64 `json:"200"`
Code204 uint64 `json:"204"`
Code206 uint64 `json:"206"`
Code302 uint64 `json:"302"`
Code400 uint64 `json:"400"`
Code403 uint64 `json:"403"`
@ -79,6 +88,33 @@ type httpCodeStatistics struct {
Code501 uint64 `json:"501"`
}
type httpProtocolStatistics struct {
Http10 uint64 `json:"http_10"`
Http11 uint64 `json:"http_11"`
Http20 uint64 `json:"http_20"`
Http30 uint64 `json:"http_30"`
Other uint64 `json:"other"`
}
type tlsVersionStatistics struct {
Tls12 uint64 `json:"tls_12"`
Tls13 uint64 `json:"tls_13"`
Other uint64 `json:"other"`
}
type tlsCipherStatistics struct {
ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 uint64 `json:"ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"`
ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 uint64 `json:"ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"`
ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 uint64 `json:"ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"`
ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 uint64 `json:"ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"`
ECDHE_RSA_WITH_AES_256_GCM_SHA384 uint64 `json:"ECDHE_RSA_WITH_AES_256_GCM_SHA384"`
ECDHE_RSA_WITH_AES_128_GCM_SHA256 uint64 `json:"ECDHE_RSA_WITH_AES_128_GCM_SHA256"`
CHACHA20_POLY1305_SHA256 uint64 `json:"CHACHA20_POLY1305_SHA256"`
AES_128_GCM_SHA256 uint64 `json:"AES_128_GCM_SHA256"`
AES_256_GCM_SHA384 uint64 `json:"AES_256_GCM_SHA384"`
Other uint64 `json:"other"`
}
var globalStatistics statistics
func getFirstValidContentEntry(entries *[]ContentEntry) *ContentEntry {
@ -180,6 +216,8 @@ func GetMimeTypeFromExtension(ext string) string {
return "text/javascript"
case "wasm":
return "application/wasm"
case "json":
return "application/json"
case "html":
return "text/html"
case "css":
@ -200,6 +238,73 @@ func GetMimeTypeFromExtension(ext string) string {
}
func handleQueryRequest(ctx httputils.RequestContext, identifier cid.Cid, extraArguments []string) {
major, minor, _ := http.ParseHTTPVersion(ctx.GetProtocol())
switch major {
case 1:
switch minor {
case 0:
atomic.AddUint64(&globalStatistics.Http.Protocol.Http10, 1)
case 1:
atomic.AddUint64(&globalStatistics.Http.Protocol.Http11, 1)
default:
atomic.AddUint64(&globalStatistics.Http.Protocol.Other, 1)
}
case 2:
switch minor {
case 0:
atomic.AddUint64(&globalStatistics.Http.Protocol.Http20, 1)
default:
atomic.AddUint64(&globalStatistics.Http.Protocol.Other, 1)
}
case 3:
switch minor {
case 0:
atomic.AddUint64(&globalStatistics.Http.Protocol.Http30, 1)
default:
atomic.AddUint64(&globalStatistics.Http.Protocol.Other, 1)
}
default:
atomic.AddUint64(&globalStatistics.Http.Protocol.Other, 1)
}
switch ctx.GetTLSVersion() {
case tls.VersionTLS12:
atomic.AddUint64(&globalStatistics.Tls.Version.Tls12, 1)
case tls.VersionTLS13:
atomic.AddUint64(&globalStatistics.Tls.Version.Tls13, 1)
default:
atomic.AddUint64(&globalStatistics.Tls.Version.Other, 1)
}
switch ctx.GetTLSCipher() {
//TLS 1.2 ciphers
case tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
atomic.AddUint64(&globalStatistics.Tls.Cipher.ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 1)
case tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
atomic.AddUint64(&globalStatistics.Tls.Cipher.ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 1)
case tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
atomic.AddUint64(&globalStatistics.Tls.Cipher.ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 1)
case tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
atomic.AddUint64(&globalStatistics.Tls.Cipher.ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 1)
case tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
atomic.AddUint64(&globalStatistics.Tls.Cipher.ECDHE_RSA_WITH_AES_256_GCM_SHA384, 1)
case tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
atomic.AddUint64(&globalStatistics.Tls.Cipher.ECDHE_RSA_WITH_AES_128_GCM_SHA256, 1)
//TLS 1.3 ciphers
case tls.TLS_CHACHA20_POLY1305_SHA256:
atomic.AddUint64(&globalStatistics.Tls.Cipher.CHACHA20_POLY1305_SHA256, 1)
case tls.TLS_AES_128_GCM_SHA256:
atomic.AddUint64(&globalStatistics.Tls.Cipher.AES_128_GCM_SHA256, 1)
case tls.TLS_AES_256_GCM_SHA384:
atomic.AddUint64(&globalStatistics.Tls.Cipher.AES_256_GCM_SHA384, 1)
default:
atomic.AddUint64(&globalStatistics.Tls.Cipher.Other, 1)
}
cTime := time.Now()
var cacheEntry = tryGetCacheEntryForIdentifier(identifier)
@ -242,12 +347,12 @@ func handleQueryRequest(ctx httputils.RequestContext, identifier cid.Cid, extraA
kind = "md5"
}
if kind != "" {
atomic.AddUint64(&globalStatistics.HttpCode.Code302, 1)
atomic.AddUint64(&globalStatistics.Http.Code.Code302, 1)
ctx.DoRedirect(fmt.Sprintf("%s/%s/%s/%s", origin, kind, hex.EncodeToString(mh.Digest), strings.Join(extraArguments, "/")), http.StatusFound)
return
}
}
atomic.AddUint64(&globalStatistics.HttpCode.Code404, 1)
atomic.AddUint64(&globalStatistics.Http.Code.Code404, 1)
ctx.SetResponseCode(http.StatusNotFound)
return
}
@ -256,7 +361,7 @@ func handleQueryRequest(ctx httputils.RequestContext, identifier cid.Cid, extraA
file, err := os.Open(cacheEntry.Entry.Path)
if err != nil {
atomic.AddUint64(&globalStatistics.HttpCode.Code404, 1)
atomic.AddUint64(&globalStatistics.Http.Code.Code404, 1)
ctx.SetResponseCode(http.StatusNotFound)
return
}
@ -286,8 +391,13 @@ func handleQueryRequest(ctx httputils.RequestContext, identifier cid.Cid, extraA
ss := &statsStream{
stream: httputils.NewStreamFromFile(file),
}
ctx.ServeStream(ss)
runtime.SetFinalizer(ss, (*statsStream).Close)
if len(ctx.GetRequestHeader("range")) > 0 {
atomic.AddUint64(&globalStatistics.Http.Code.Code206, 1)
} else {
atomic.AddUint64(&globalStatistics.Http.Code.Code200, 1)
}
ctx.ServeStream(ss)
}
type statsStream struct {
@ -409,7 +519,7 @@ func IsTrustedPublicKey(key ed25519.PublicKey) bool {
func handle(ctx httputils.RequestContext) {
if len(ctx.GetHost()) > 0 && len(ctx.GetTLSServerName()) > 0 && strings.Split(ctx.GetHost(), ":")[0] != ctx.GetTLSServerName() { //Prevents rebinding / DNS stuff
atomic.AddUint64(&globalStatistics.HttpCode.Code404, 1)
atomic.AddUint64(&globalStatistics.Http.Code.Code404, 1)
ctx.SetResponseCode(http.StatusNotFound)
return
}
@ -429,13 +539,15 @@ func handle(ctx httputils.RequestContext) {
if ctx.GetPath() == "/stats" {
ctx.SetResponseHeader("Content-Type", "application/json")
atomic.AddUint64(&globalStatistics.HttpCode.Code200, 1)
atomic.AddUint64(&globalStatistics.Http.Code.Code200, 1)
ctx.SetResponseCode(http.StatusOK)
statsStruct := struct {
Statistics *statistics `json:"statistics"`
TotalEntries uint64 `json:"total_entries"`
TotalSize uint64 `json:"total_size"`
Statistics *statistics `json:"statistics"`
Database struct {
TotalEntries uint64 `json:"entries"`
TotalSize uint64 `json:"size"`
} `json:"database"`
}{
Statistics: &globalStatistics,
}
@ -444,7 +556,7 @@ func handle(ctx httputils.RequestContext) {
defer rows.Close()
if rows.Next() {
rows.Scan(&statsStruct.TotalEntries, &statsStruct.TotalSize)
rows.Scan(&statsStruct.Database.TotalEntries, &statsStruct.Database.TotalSize)
}
}
@ -455,26 +567,26 @@ func handle(ctx httputils.RequestContext) {
pathElements := strings.Split(ctx.GetPath(), "/")
if len(pathElements) < 2 {
atomic.AddUint64(&globalStatistics.HttpCode.Code400, 1)
atomic.AddUint64(&globalStatistics.Http.Code.Code400, 1)
ctx.SetResponseCode(http.StatusBadRequest)
return
}
messageBytes, err := MakyuuIchaival.Bech32Encoding.DecodeString(pathElements[1])
if err != nil {
atomic.AddUint64(&globalStatistics.HttpCode.Code400, 1)
atomic.AddUint64(&globalStatistics.Http.Code.Code400, 1)
ctx.SetResponseCode(http.StatusBadRequest)
return
}
message := contentmessage.DecodeContentMessage(messageBytes)
if message == nil {
atomic.AddUint64(&globalStatistics.HttpCode.Code400, 1)
atomic.AddUint64(&globalStatistics.Http.Code.Code400, 1)
ctx.SetResponseCode(http.StatusBadRequest)
return
}
if !IsTrustedPublicKey(message.PublicKey) {
atomic.AddUint64(&globalStatistics.HttpCode.Code403, 1)
atomic.AddUint64(&globalStatistics.Http.Code.Code403, 1)
ctx.SetResponseCode(http.StatusForbidden)
return
}
@ -497,7 +609,7 @@ func handle(ctx httputils.RequestContext) {
ctx.AddTiming("v", "Ed25519 Verify", cTime.Sub(pTime))
if !result {
atomic.AddUint64(&globalStatistics.HttpCode.Code403, 1)
atomic.AddUint64(&globalStatistics.Http.Code.Code403, 1)
ctx.SetResponseCode(http.StatusForbidden)
return
}
@ -511,10 +623,10 @@ func handle(ctx httputils.RequestContext) {
setOtherHeaders(ctx)
setCORSHeaders(ctx)
atomic.AddUint64(&globalStatistics.HttpCode.Code204, 1)
atomic.AddUint64(&globalStatistics.Http.Code.Code204, 1)
ctx.SetResponseCode(http.StatusNoContent)
} else {
atomic.AddUint64(&globalStatistics.HttpCode.Code501, 1)
atomic.AddUint64(&globalStatistics.Http.Code.Code501, 1)
ctx.SetResponseCode(http.StatusNotImplemented)
}
}

9
go.mod
View file

@ -3,7 +3,7 @@ module git.gammaspectra.live/S.O.N.G/OrbitalBeat
go 1.18
require (
git.gammaspectra.live/S.O.N.G/MakyuuIchaival v0.0.0-20220609161446-6d2dcedf6cdb
git.gammaspectra.live/S.O.N.G/MakyuuIchaival v0.0.0-20220610084655-771c39a29ce8
github.com/cloudflare/circl v1.1.0
github.com/ipfs/go-cid v0.2.0
github.com/lib/pq v1.10.6
@ -18,7 +18,7 @@ require (
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/klauspost/compress v1.15.6 // indirect
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
github.com/lucas-clemente/quic-go v0.27.2 // indirect
github.com/lucas-clemente/quic-go v0.27.1-0.20220609183220-6fbc6d951a40 // indirect
github.com/marten-seemann/qpack v0.2.1 // indirect
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect
@ -37,12 +37,11 @@ require (
github.com/valyala/fasthttp v1.37.0 // indirect
github.com/valyala/fastrand v1.1.0 // indirect
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.0.0-20220607020251-c690dde0001d // indirect
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.10 // indirect
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
golang.org/x/tools v0.1.11 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
lukechampine.com/blake3 v1.1.7 // indirect
)

18
go.sum
View file

@ -7,8 +7,8 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
git.gammaspectra.live/S.O.N.G/MakyuuIchaival v0.0.0-20220609161446-6d2dcedf6cdb h1:VNExZ2u32hQqBhk+PqtxbsQdx9NalDrvcqhM3gHff9U=
git.gammaspectra.live/S.O.N.G/MakyuuIchaival v0.0.0-20220609161446-6d2dcedf6cdb/go.mod h1:3n5dC5y2CX+78/fpEx/3NK4ySxoOycKLuaGR4tRuSyA=
git.gammaspectra.live/S.O.N.G/MakyuuIchaival v0.0.0-20220610084655-771c39a29ce8 h1:Xp0G4r0EwoqIu/s/4i7LAjGBjkaUnKtGX1TMkvMhvYs=
git.gammaspectra.live/S.O.N.G/MakyuuIchaival v0.0.0-20220610084655-771c39a29ce8/go.mod h1:hyfgWb6jQhsy7hrFDRq/sYLa3J3zYj7VvhHRC8ZLUlw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
@ -95,8 +95,8 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs=
github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lucas-clemente/quic-go v0.27.2 h1:zsMwwniyybb8B/UDNXRSYee7WpQJVOcjQEGgpw2ikXs=
github.com/lucas-clemente/quic-go v0.27.2/go.mod h1:vXgO/11FBSKM+js1NxoaQ/bPtVFYfB7uxhfHXyMhl1A=
github.com/lucas-clemente/quic-go v0.27.1-0.20220609183220-6fbc6d951a40 h1:TmAcxgYB4Vnpa8QfljlEFYv8BHfjrkZlTzQAvCSRTKU=
github.com/lucas-clemente/quic-go v0.27.1-0.20220609183220-6fbc6d951a40/go.mod h1:vXgO/11FBSKM+js1NxoaQ/bPtVFYfB7uxhfHXyMhl1A=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs=
@ -222,8 +222,8 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -307,14 +307,12 @@ golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY=
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0=
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=