mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-07-14 07:47:24 +02:00
Merge branch 'feat-scim' into test-build
This commit is contained in:
@@ -73,6 +73,18 @@ const (
|
||||
ROUTE_V1_COMPANY_ID = "/api/v1/company/:id"
|
||||
ROUTE_V1_COMPANY_ID_EXPORT = "/api/v1/company/:id/export"
|
||||
ROUTE_V1_COMPANY_ID_EXPORT_SHARED = "/api/v1/company/shared/export"
|
||||
ROUTE_V1_COMPANY_SCIM = "/api/v1/company/scim/:companyID"
|
||||
ROUTE_V1_COMPANY_SCIM_TOKEN = "/api/v1/company/scim/:companyID/token"
|
||||
// scim v2 provisioning endpoints (public — authenticated via bearer token)
|
||||
ROUTE_SCIM_V2_SERVICE_PROVIDER_CONFIG = "/api/v1/scim/v2/:companyID/ServiceProviderConfig"
|
||||
ROUTE_SCIM_V2_RESOURCE_TYPES = "/api/v1/scim/v2/:companyID/ResourceTypes"
|
||||
ROUTE_SCIM_V2_SCHEMAS = "/api/v1/scim/v2/:companyID/Schemas"
|
||||
ROUTE_SCIM_V2_SCHEMA_ID = "/api/v1/scim/v2/:companyID/Schemas/:schemaID"
|
||||
ROUTE_SCIM_V2_RESOURCE_TYPE_ID = "/api/v1/scim/v2/:companyID/ResourceTypes/:resourceTypeID"
|
||||
ROUTE_SCIM_V2_USERS = "/api/v1/scim/v2/:companyID/Users"
|
||||
ROUTE_SCIM_V2_USER_ID = "/api/v1/scim/v2/:companyID/Users/:userID"
|
||||
ROUTE_SCIM_V2_GROUPS = "/api/v1/scim/v2/:companyID/Groups"
|
||||
ROUTE_SCIM_V2_GROUP_ID = "/api/v1/scim/v2/:companyID/Groups/:groupID"
|
||||
// option
|
||||
ROUTE_V1_OPTION = "/api/v1/option"
|
||||
ROUTE_V1_OPTION_GET = "/api/v1/option/:key"
|
||||
@@ -243,20 +255,47 @@ func (a *administrationServer) Router() *gin.Engine {
|
||||
return a.router
|
||||
}
|
||||
|
||||
// setupRoutes sets up the routes for the administration app
|
||||
// setupRoutes sets up the routes for the administration app.
|
||||
// ip-protected admin routes are registered under a group that applies the
|
||||
// IPLimiter middleware. scim v2 provisioning routes are registered directly on
|
||||
// the engine so that external identity providers are never blocked by the
|
||||
// admin ip allowlist.
|
||||
func setupRoutes(
|
||||
r *gin.Engine,
|
||||
controllers *Controllers,
|
||||
middleware *Middlewares,
|
||||
) *gin.Engine {
|
||||
// scim v2 provisioning endpoints — no ip allowlist, bearer-token auth only
|
||||
r.
|
||||
GET(ROUTE_SCIM_V2_SERVICE_PROVIDER_CONFIG, controllers.Scim.ServiceProviderConfig).
|
||||
GET(ROUTE_SCIM_V2_RESOURCE_TYPES, controllers.Scim.ResourceTypes).
|
||||
GET(ROUTE_SCIM_V2_SCHEMAS, controllers.Scim.Schemas).
|
||||
GET(ROUTE_SCIM_V2_SCHEMA_ID, controllers.Scim.GetSchema).
|
||||
GET(ROUTE_SCIM_V2_RESOURCE_TYPE_ID, controllers.Scim.GetResourceType).
|
||||
GET(ROUTE_SCIM_V2_USERS, controllers.Scim.ListUsers).
|
||||
POST(ROUTE_SCIM_V2_USERS, controllers.Scim.CreateUser).
|
||||
GET(ROUTE_SCIM_V2_USER_ID, controllers.Scim.GetUser).
|
||||
PUT(ROUTE_SCIM_V2_USER_ID, controllers.Scim.ReplaceUser).
|
||||
PATCH(ROUTE_SCIM_V2_USER_ID, controllers.Scim.PatchUser).
|
||||
DELETE(ROUTE_SCIM_V2_USER_ID, controllers.Scim.DeleteUser).
|
||||
GET(ROUTE_SCIM_V2_GROUPS, controllers.Scim.ListGroups).
|
||||
POST(ROUTE_SCIM_V2_GROUPS, controllers.Scim.CreateGroup).
|
||||
GET(ROUTE_SCIM_V2_GROUP_ID, controllers.Scim.GetGroup).
|
||||
PUT(ROUTE_SCIM_V2_GROUP_ID, controllers.Scim.ReplaceGroup).
|
||||
PATCH(ROUTE_SCIM_V2_GROUP_ID, controllers.Scim.PatchGroup).
|
||||
DELETE(ROUTE_SCIM_V2_GROUP_ID, controllers.Scim.DeleteGroup)
|
||||
|
||||
// all other admin routes are protected by the ip allowlist middleware
|
||||
admin := r.Group("/", middleware.IPLimiter)
|
||||
_ = admin
|
||||
|
||||
if !build.Flags.Production {
|
||||
r.
|
||||
admin.
|
||||
GET("/api/v1/_debug/panic", middleware.SessionHandler, controllers.Log.Panic).
|
||||
GET("/api/v1/_debug/slow", middleware.SessionHandler, controllers.Log.Slow)
|
||||
}
|
||||
|
||||
r.
|
||||
admin.
|
||||
// log
|
||||
GET(ROUTE_V1_LOG, middleware.SessionHandler, controllers.Log.GetLevel).
|
||||
POST(ROUTE_V1_LOG, middleware.SessionHandler, controllers.Log.SetLevel).
|
||||
@@ -313,6 +352,11 @@ func setupRoutes(
|
||||
GET(ROUTE_V1_COMPANY_ID_EXPORT_SHARED, middleware.SessionHandler, controllers.Company.ExportShared).
|
||||
GET(ROUTE_V1_COMPANY_ID, middleware.SessionHandler, controllers.Company.GetByID).
|
||||
DELETE(ROUTE_V1_COMPANY_ID, middleware.SessionHandler, controllers.Company.DeleteByID).
|
||||
// company scim
|
||||
GET(ROUTE_V1_COMPANY_SCIM, middleware.SessionHandler, controllers.CompanyScimConfig.GetByCompanyID).
|
||||
POST(ROUTE_V1_COMPANY_SCIM, middleware.SessionHandler, controllers.CompanyScimConfig.Upsert).
|
||||
DELETE(ROUTE_V1_COMPANY_SCIM, middleware.SessionHandler, controllers.CompanyScimConfig.Delete).
|
||||
POST(ROUTE_V1_COMPANY_SCIM_TOKEN, middleware.SessionHandler, controllers.CompanyScimConfig.RotateToken).
|
||||
// options
|
||||
GET(ROUTE_V1_OPTION_GET, middleware.SessionHandler, controllers.Option.Get).
|
||||
POST(ROUTE_V1_OPTION, middleware.SessionHandler, middleware.SessionHandler, controllers.Option.Update).
|
||||
|
||||
@@ -40,6 +40,8 @@ type Controllers struct {
|
||||
Backup *controller.Backup
|
||||
IPAllowList *controller.IPAllowList
|
||||
OAuthProvider *controller.OAuthProvider
|
||||
CompanyScimConfig *controller.CompanyScimConfig
|
||||
Scim *controller.Scim
|
||||
}
|
||||
|
||||
// NewControllers creates a collection of controllers
|
||||
@@ -196,6 +198,14 @@ func NewControllers(
|
||||
OAuthProviderService: services.OAuthProvider,
|
||||
Config: conf,
|
||||
}
|
||||
companyScimConfig := &controller.CompanyScimConfig{
|
||||
Common: common,
|
||||
CompanyScimConfigService: services.CompanyScimConfig,
|
||||
}
|
||||
scim := &controller.Scim{
|
||||
Common: common,
|
||||
ScimService: services.Scim,
|
||||
}
|
||||
|
||||
return &Controllers{
|
||||
Asset: asset,
|
||||
@@ -229,5 +239,7 @@ func NewControllers(
|
||||
Backup: backup,
|
||||
IPAllowList: ipAllowList,
|
||||
OAuthProvider: oauthProvider,
|
||||
CompanyScimConfig: companyScimConfig,
|
||||
Scim: scim,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ type Repositories struct {
|
||||
OAuthProvider *repository.OAuthProvider
|
||||
OAuthState *repository.OAuthState
|
||||
MicrosoftDeviceCode *repository.MicrosoftDeviceCode
|
||||
CompanyScimConfig *repository.CompanyScimConfig
|
||||
}
|
||||
|
||||
// NewRepositories creates a collection of repositories
|
||||
@@ -63,5 +64,6 @@ func NewRepositories(
|
||||
OAuthProvider: &repository.OAuthProvider{DB: db},
|
||||
OAuthState: &repository.OAuthState{DB: db},
|
||||
MicrosoftDeviceCode: &repository.MicrosoftDeviceCode{DB: db},
|
||||
CompanyScimConfig: &repository.CompanyScimConfig{DB: db},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ type Services struct {
|
||||
ProxySessionManager *service.ProxySessionManager
|
||||
OAuthProvider *service.OAuthProvider
|
||||
MicrosoftDeviceCode *service.MicrosoftDeviceCode
|
||||
CompanyScimConfig *service.CompanyScimConfig
|
||||
Scim *service.Scim
|
||||
}
|
||||
|
||||
// NewServices creates a collection of services
|
||||
@@ -275,8 +277,22 @@ func NewServices(
|
||||
|
||||
// inject oauth provider service into api sender
|
||||
apiSender.OAuthProviderService = oauthProvider
|
||||
companyScimConfig := &service.CompanyScimConfig{
|
||||
Common: common,
|
||||
CompanyScimConfigRepository: repositories.CompanyScimConfig,
|
||||
}
|
||||
scim := &service.Scim{
|
||||
Common: common,
|
||||
CompanyScimConfigRepository: repositories.CompanyScimConfig,
|
||||
CompanyScimConfigService: companyScimConfig,
|
||||
RecipientRepository: repositories.Recipient,
|
||||
RecipientGroupRepository: repositories.RecipientGroup,
|
||||
CampaignRepository: repositories.Campaign,
|
||||
}
|
||||
|
||||
return &Services{
|
||||
CompanyScimConfig: companyScimConfig,
|
||||
Scim: scim,
|
||||
Asset: asset,
|
||||
Attachment: attachment,
|
||||
Company: companyService,
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/phishingclub/phishingclub/errs"
|
||||
"github.com/phishingclub/phishingclub/service"
|
||||
)
|
||||
|
||||
// CompanyScimConfig is the SCIM configuration controller
|
||||
type CompanyScimConfig struct {
|
||||
Common
|
||||
CompanyScimConfigService *service.CompanyScimConfig
|
||||
}
|
||||
|
||||
// upsertScimRequest is the request body for the Upsert handler
|
||||
type upsertScimRequest struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// GetByCompanyID returns the SCIM configuration for the given company
|
||||
func (c *CompanyScimConfig) GetByCompanyID(g *gin.Context) {
|
||||
session, _, ok := c.handleSession(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// parse companyID from url param
|
||||
companyID, err := uuid.Parse(g.Param("companyID"))
|
||||
if err != nil {
|
||||
c.Logger.Debugw("failed to parse companyID param", "error", err)
|
||||
c.Response.BadRequestMessage(g, errs.MsgFailedToParseUUID)
|
||||
return
|
||||
}
|
||||
// get
|
||||
config, err := c.CompanyScimConfigService.GetByCompanyID(
|
||||
g.Request.Context(),
|
||||
session,
|
||||
&companyID,
|
||||
)
|
||||
// handle response
|
||||
if ok := c.handleErrors(g, err); !ok {
|
||||
return
|
||||
}
|
||||
c.Response.OK(g, config)
|
||||
}
|
||||
|
||||
// Upsert creates or updates the SCIM configuration for the given company
|
||||
func (c *CompanyScimConfig) Upsert(g *gin.Context) {
|
||||
session, _, ok := c.handleSession(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// parse companyID from url param
|
||||
companyID, err := uuid.Parse(g.Param("companyID"))
|
||||
if err != nil {
|
||||
c.Logger.Debugw("failed to parse companyID param", "error", err)
|
||||
c.Response.BadRequestMessage(g, errs.MsgFailedToParseUUID)
|
||||
return
|
||||
}
|
||||
// parse request body
|
||||
var req upsertScimRequest
|
||||
if ok := c.handleParseRequest(g, &req); !ok {
|
||||
return
|
||||
}
|
||||
// upsert
|
||||
result, err := c.CompanyScimConfigService.Upsert(
|
||||
g.Request.Context(),
|
||||
session,
|
||||
&companyID,
|
||||
req.Enabled,
|
||||
)
|
||||
// handle response
|
||||
if ok := c.handleErrors(g, err); !ok {
|
||||
return
|
||||
}
|
||||
c.Response.OK(g, result)
|
||||
}
|
||||
|
||||
// RotateToken generates a new bearer token for the given company's SCIM config.
|
||||
// the plain token is returned once and must be stored by the caller.
|
||||
func (c *CompanyScimConfig) RotateToken(g *gin.Context) {
|
||||
session, _, ok := c.handleSession(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// parse companyID from url param
|
||||
companyID, err := uuid.Parse(g.Param("companyID"))
|
||||
if err != nil {
|
||||
c.Logger.Debugw("failed to parse companyID param", "error", err)
|
||||
c.Response.BadRequestMessage(g, errs.MsgFailedToParseUUID)
|
||||
return
|
||||
}
|
||||
// rotate
|
||||
result, err := c.CompanyScimConfigService.RotateToken(
|
||||
g.Request.Context(),
|
||||
session,
|
||||
&companyID,
|
||||
)
|
||||
// handle response
|
||||
if ok := c.handleErrors(g, err); !ok {
|
||||
return
|
||||
}
|
||||
c.Response.OK(g, result)
|
||||
}
|
||||
|
||||
// Delete removes the SCIM configuration for the given company
|
||||
func (c *CompanyScimConfig) Delete(g *gin.Context) {
|
||||
session, _, ok := c.handleSession(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// parse companyID from url param
|
||||
companyID, err := uuid.Parse(g.Param("companyID"))
|
||||
if err != nil {
|
||||
c.Logger.Debugw("failed to parse companyID param", "error", err)
|
||||
c.Response.BadRequestMessage(g, errs.MsgFailedToParseUUID)
|
||||
return
|
||||
}
|
||||
// delete
|
||||
err = c.CompanyScimConfigService.DeleteByCompanyID(
|
||||
g.Request.Context(),
|
||||
session,
|
||||
&companyID,
|
||||
)
|
||||
// handle response
|
||||
if ok := c.handleErrors(g, err); !ok {
|
||||
return
|
||||
}
|
||||
c.Response.OK(g, gin.H{})
|
||||
}
|
||||
@@ -0,0 +1,677 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/google/uuid"
|
||||
"github.com/phishingclub/phishingclub/errs"
|
||||
"github.com/phishingclub/phishingclub/service"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
scimContentType = "application/scim+json"
|
||||
)
|
||||
|
||||
// Scim is the SCIM v2 protocol controller.
|
||||
// all handlers authenticate via bearer token before dispatching to the service layer.
|
||||
type Scim struct {
|
||||
Common
|
||||
ScimService *service.Scim
|
||||
}
|
||||
|
||||
// scimError writes a SCIM-compliant error response
|
||||
func scimError(g *gin.Context, status int, detail string, scimType ...string) {
|
||||
body := service.ScimError{
|
||||
Schemas: []string{"urn:ietf:params:scim:api:messages:2.0:Error"},
|
||||
Status: status,
|
||||
Detail: detail,
|
||||
}
|
||||
if len(scimType) > 0 {
|
||||
body.ScimType = scimType[0]
|
||||
}
|
||||
g.Header("Content-Type", scimContentType)
|
||||
g.JSON(status, body)
|
||||
g.Abort()
|
||||
}
|
||||
|
||||
// scimOK writes a SCIM-compliant success response with Content-Type application/scim+json
|
||||
func scimOK(g *gin.Context, status int, body any) {
|
||||
g.Header("Content-Type", scimContentType)
|
||||
g.JSON(status, body)
|
||||
}
|
||||
|
||||
// authenticate validates the Authorization: Bearer <token> header and returns
|
||||
// the verified SCIM config for the company. on failure it writes the error
|
||||
// response itself and returns nil, false.
|
||||
func (c *Scim) authenticate(g *gin.Context, companyID *uuid.UUID) (*service.ScimConfigResult, bool) {
|
||||
authHeader := g.GetHeader("Authorization")
|
||||
const bearerPrefix = "Bearer "
|
||||
if !strings.HasPrefix(authHeader, bearerPrefix) {
|
||||
scimError(g, http.StatusUnauthorized, "missing or malformed Authorization header")
|
||||
return nil, false
|
||||
}
|
||||
token := strings.TrimPrefix(authHeader, bearerPrefix)
|
||||
if token == "" {
|
||||
scimError(g, http.StatusUnauthorized, "empty bearer token")
|
||||
return nil, false
|
||||
}
|
||||
|
||||
config, authed, err := c.ScimService.VerifyAndLoadConfig(g.Request.Context(), companyID, token)
|
||||
if err != nil {
|
||||
if isNotFound(err) {
|
||||
scimError(g, http.StatusUnauthorized, "no SCIM configuration found for this company")
|
||||
return nil, false
|
||||
}
|
||||
c.Logger.Errorw("scim auth: error verifying token", "error", err)
|
||||
scimError(g, http.StatusInternalServerError, "internal error during authentication")
|
||||
return nil, false
|
||||
}
|
||||
if !authed {
|
||||
scimError(g, http.StatusUnauthorized, "invalid bearer token or SCIM provisioning is disabled")
|
||||
return nil, false
|
||||
}
|
||||
return &service.ScimConfigResult{Config: config, CompanyID: companyID}, true
|
||||
}
|
||||
|
||||
// parseCompanyID extracts and parses the :companyID path parameter.
|
||||
// writes the error response and returns false on failure.
|
||||
func (c *Scim) parseCompanyID(g *gin.Context) (*uuid.UUID, bool) {
|
||||
id, err := uuid.Parse(g.Param("companyID"))
|
||||
if err != nil {
|
||||
scimError(g, http.StatusBadRequest, "invalid companyID in URL path")
|
||||
return nil, false
|
||||
}
|
||||
return &id, true
|
||||
}
|
||||
|
||||
// parseUserID extracts and parses the :userID path parameter.
|
||||
// an unparseable id means the resource cannot exist, so 404 is correct per rfc 7644.
|
||||
func (c *Scim) parseUserID(g *gin.Context) (*uuid.UUID, bool) {
|
||||
id, err := uuid.Parse(g.Param("userID"))
|
||||
if err != nil {
|
||||
scimError(g, http.StatusNotFound, "user not found")
|
||||
return nil, false
|
||||
}
|
||||
return &id, true
|
||||
}
|
||||
|
||||
// parseGroupID extracts and parses the :groupID path parameter.
|
||||
// an unparseable id means the resource cannot exist, so 404 is correct per rfc 7644.
|
||||
func (c *Scim) parseGroupID(g *gin.Context) (*uuid.UUID, bool) {
|
||||
id, err := uuid.Parse(g.Param("groupID"))
|
||||
if err != nil {
|
||||
scimError(g, http.StatusNotFound, "group not found")
|
||||
return nil, false
|
||||
}
|
||||
return &id, true
|
||||
}
|
||||
|
||||
// scimBaseURL builds the base URL for SCIM resource locations from the request
|
||||
func scimBaseURL(g *gin.Context, companyID *uuid.UUID) string {
|
||||
scheme := "https"
|
||||
if g.Request.TLS == nil {
|
||||
scheme = "http"
|
||||
}
|
||||
return scheme + "://" + g.Request.Host + "/api/v1/scim/v2/" + companyID.String()
|
||||
}
|
||||
|
||||
// isNotFound returns true when the error wraps gorm.ErrRecordNotFound
|
||||
func isNotFound(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(err.Error(), gorm.ErrRecordNotFound.Error())
|
||||
}
|
||||
|
||||
// ── ServiceProviderConfig ─────────────────────────────────────────────────────
|
||||
|
||||
// ServiceProviderConfig handles GET /scim/v2/:companyID/ServiceProviderConfig
|
||||
func (c *Scim) ServiceProviderConfig(g *gin.Context) {
|
||||
companyID, ok := c.parseCompanyID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if _, ok := c.authenticate(g, companyID); !ok {
|
||||
return
|
||||
}
|
||||
base := scimBaseURL(g, companyID)
|
||||
scimOK(g, http.StatusOK, c.ScimService.ServiceProviderConfig(base))
|
||||
}
|
||||
|
||||
// ── Schemas ───────────────────────────────────────────────────────────────────
|
||||
|
||||
// Schemas handles GET /api/v1/scim/v2/:companyID/Schemas
|
||||
func (c *Scim) Schemas(g *gin.Context) {
|
||||
companyID, ok := c.parseCompanyID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if _, ok := c.authenticate(g, companyID); !ok {
|
||||
return
|
||||
}
|
||||
base := scimBaseURL(g, companyID)
|
||||
schemas := c.ScimService.Schemas(base)
|
||||
scimOK(g, http.StatusOK, gin.H{
|
||||
"schemas": []string{"urn:ietf:params:scim:api:messages:2.0:ListResponse"},
|
||||
"totalResults": len(schemas),
|
||||
"startIndex": 1,
|
||||
"itemsPerPage": len(schemas),
|
||||
"Resources": schemas,
|
||||
})
|
||||
}
|
||||
|
||||
// GetSchema handles GET /api/v1/scim/v2/:companyID/Schemas/:schemaID
|
||||
func (c *Scim) GetSchema(g *gin.Context) {
|
||||
companyID, ok := c.parseCompanyID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if _, ok := c.authenticate(g, companyID); !ok {
|
||||
return
|
||||
}
|
||||
schemaID := g.Param("schemaID")
|
||||
base := scimBaseURL(g, companyID)
|
||||
schema, found := c.ScimService.GetSchemaByID(base, schemaID)
|
||||
if !found {
|
||||
scimError(g, http.StatusNotFound, "schema not found")
|
||||
return
|
||||
}
|
||||
scimOK(g, http.StatusOK, schema)
|
||||
}
|
||||
|
||||
// ── ResourceTypes ─────────────────────────────────────────────────────────────
|
||||
|
||||
// ResourceTypes handles GET /scim/v2/:companyID/ResourceTypes
|
||||
func (c *Scim) ResourceTypes(g *gin.Context) {
|
||||
companyID, ok := c.parseCompanyID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if _, ok := c.authenticate(g, companyID); !ok {
|
||||
return
|
||||
}
|
||||
base := scimBaseURL(g, companyID)
|
||||
types := c.ScimService.ResourceTypes(base)
|
||||
scimOK(g, http.StatusOK, gin.H{
|
||||
"schemas": []string{"urn:ietf:params:scim:api:messages:2.0:ListResponse"},
|
||||
"totalResults": len(types),
|
||||
"startIndex": 1,
|
||||
"itemsPerPage": len(types),
|
||||
"Resources": types,
|
||||
})
|
||||
}
|
||||
|
||||
// GetResourceType handles GET /scim/v2/:companyID/ResourceTypes/:resourceTypeID
|
||||
func (c *Scim) GetResourceType(g *gin.Context) {
|
||||
companyID, ok := c.parseCompanyID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if _, ok := c.authenticate(g, companyID); !ok {
|
||||
return
|
||||
}
|
||||
resourceTypeID := g.Param("resourceTypeID")
|
||||
base := scimBaseURL(g, companyID)
|
||||
rt, found := c.ScimService.GetResourceTypeByID(base, resourceTypeID)
|
||||
if !found {
|
||||
scimError(g, http.StatusNotFound, "resource type not found")
|
||||
return
|
||||
}
|
||||
scimOK(g, http.StatusOK, rt)
|
||||
}
|
||||
|
||||
// ── Groups ────────────────────────────────────────────────────────────────────
|
||||
|
||||
// ListGroups handles GET /api/v1/scim/v2/:companyID/Groups
|
||||
func (c *Scim) ListGroups(g *gin.Context) {
|
||||
companyID, ok := c.parseCompanyID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
result, ok := c.authenticate(g, companyID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
base := scimBaseURL(g, companyID)
|
||||
startIndex := parseIntQuery(g, "startIndex", 1)
|
||||
count := parseIntQuery(g, "count", 0)
|
||||
filter := g.Query("filter")
|
||||
excludedAttributes := g.Query("excludedAttributes")
|
||||
|
||||
list, err := c.ScimService.ListGroupsRaw(g.Request.Context(), companyID, result.Config, base, startIndex, count, filter, excludedAttributes)
|
||||
if err != nil {
|
||||
c.Logger.Errorw("scim list groups: error", "error", err)
|
||||
scimError(g, http.StatusInternalServerError, "failed to list groups")
|
||||
return
|
||||
}
|
||||
scimOK(g, http.StatusOK, list)
|
||||
}
|
||||
|
||||
// GetGroup handles GET /api/v1/scim/v2/:companyID/Groups/:groupID
|
||||
func (c *Scim) GetGroup(g *gin.Context) {
|
||||
companyID, ok := c.parseCompanyID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
result, ok := c.authenticate(g, companyID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
groupID, ok := c.parseGroupID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
base := scimBaseURL(g, companyID)
|
||||
|
||||
group, err := c.ScimService.GetGroup(g.Request.Context(), companyID, result.Config, groupID, base)
|
||||
if err != nil {
|
||||
if isNotFound(err) {
|
||||
scimError(g, http.StatusNotFound, "group not found")
|
||||
return
|
||||
}
|
||||
c.Logger.Errorw("scim get group: error", "error", err)
|
||||
scimError(g, http.StatusInternalServerError, "failed to get group")
|
||||
return
|
||||
}
|
||||
scimOK(g, http.StatusOK, group)
|
||||
}
|
||||
|
||||
// CreateGroup handles POST /api/v1/scim/v2/:companyID/Groups
|
||||
func (c *Scim) CreateGroup(g *gin.Context) {
|
||||
companyID, ok := c.parseCompanyID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
result, ok := c.authenticate(g, companyID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var req service.ScimGroup
|
||||
if err := g.ShouldBindJSON(&req); err != nil {
|
||||
scimError(g, http.StatusBadRequest, "invalid request body: "+err.Error(), "invalidSyntax")
|
||||
return
|
||||
}
|
||||
|
||||
base := scimBaseURL(g, companyID)
|
||||
created, err := c.ScimService.CreateGroup(g.Request.Context(), companyID, result.Config, &req, base)
|
||||
if err != nil {
|
||||
if isSyntaxError(err) {
|
||||
scimError(g, http.StatusBadRequest, err.Error(), "invalidSyntax")
|
||||
return
|
||||
}
|
||||
if isConflictError(err) {
|
||||
scimError(g, http.StatusConflict, err.Error(), "uniqueness")
|
||||
return
|
||||
}
|
||||
if isValidationError(err) {
|
||||
scimError(g, http.StatusBadRequest, err.Error(), "invalidValue")
|
||||
return
|
||||
}
|
||||
c.Logger.Errorw("scim create group: error", "error", err)
|
||||
scimError(g, http.StatusInternalServerError, "failed to create group")
|
||||
return
|
||||
}
|
||||
go c.ScimService.UpdateLastSync(context.Background(), result.Config)
|
||||
scimOK(g, http.StatusCreated, created)
|
||||
}
|
||||
|
||||
// ReplaceGroup handles PUT /api/v1/scim/v2/:companyID/Groups/:groupID
|
||||
func (c *Scim) ReplaceGroup(g *gin.Context) {
|
||||
companyID, ok := c.parseCompanyID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
result, ok := c.authenticate(g, companyID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
groupID, ok := c.parseGroupID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var req service.ScimGroup
|
||||
if err := g.ShouldBindJSON(&req); err != nil {
|
||||
scimError(g, http.StatusBadRequest, "invalid request body: "+err.Error(), "invalidValue")
|
||||
return
|
||||
}
|
||||
|
||||
base := scimBaseURL(g, companyID)
|
||||
updated, err := c.ScimService.ReplaceGroup(g.Request.Context(), companyID, result.Config, groupID, &req, base)
|
||||
if err != nil {
|
||||
if isNotFound(err) {
|
||||
scimError(g, http.StatusNotFound, "group not found")
|
||||
return
|
||||
}
|
||||
if isValidationError(err) {
|
||||
scimError(g, http.StatusBadRequest, err.Error(), "invalidValue")
|
||||
return
|
||||
}
|
||||
c.Logger.Errorw("scim replace group: error", "error", err)
|
||||
scimError(g, http.StatusInternalServerError, "failed to replace group")
|
||||
return
|
||||
}
|
||||
go c.ScimService.UpdateLastSync(context.Background(), result.Config)
|
||||
scimOK(g, http.StatusOK, updated)
|
||||
}
|
||||
|
||||
// PatchGroup handles PATCH /api/v1/scim/v2/:companyID/Groups/:groupID
|
||||
func (c *Scim) PatchGroup(g *gin.Context) {
|
||||
companyID, ok := c.parseCompanyID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
result, ok := c.authenticate(g, companyID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
groupID, ok := c.parseGroupID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var req service.ScimGroupPatchOp
|
||||
if err := g.ShouldBindJSON(&req); err != nil {
|
||||
scimError(g, http.StatusBadRequest, "invalid patch body: "+err.Error(), "invalidValue")
|
||||
return
|
||||
}
|
||||
|
||||
base := scimBaseURL(g, companyID)
|
||||
updated, err := c.ScimService.PatchGroup(g.Request.Context(), companyID, result.Config, groupID, &req, base)
|
||||
if err != nil {
|
||||
if isNotFound(err) {
|
||||
scimError(g, http.StatusNotFound, "group not found")
|
||||
return
|
||||
}
|
||||
if isValidationError(err) {
|
||||
scimError(g, http.StatusBadRequest, err.Error(), "invalidValue")
|
||||
return
|
||||
}
|
||||
c.Logger.Errorw("scim patch group: error", "error", err)
|
||||
scimError(g, http.StatusInternalServerError, "failed to patch group")
|
||||
return
|
||||
}
|
||||
go c.ScimService.UpdateLastSync(context.Background(), result.Config)
|
||||
scimOK(g, http.StatusOK, updated)
|
||||
}
|
||||
|
||||
// DeleteGroup handles DELETE /api/v1/scim/v2/:companyID/Groups/:groupID
|
||||
func (c *Scim) DeleteGroup(g *gin.Context) {
|
||||
companyID, ok := c.parseCompanyID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
result, ok := c.authenticate(g, companyID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
groupID, ok := c.parseGroupID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
err := c.ScimService.DeleteGroup(g.Request.Context(), companyID, result.Config, groupID)
|
||||
if err != nil {
|
||||
if isNotFound(err) {
|
||||
scimError(g, http.StatusNotFound, "group not found")
|
||||
return
|
||||
}
|
||||
c.Logger.Errorw("scim delete group: error", "error", err)
|
||||
scimError(g, http.StatusInternalServerError, "failed to delete group")
|
||||
return
|
||||
}
|
||||
go c.ScimService.UpdateLastSync(context.Background(), result.Config)
|
||||
// rfc 7644 §3.6 — successful DELETE returns 204 No Content
|
||||
g.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// ── Users ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
// ListUsers handles GET /scim/v2/:companyID/Users
|
||||
func (c *Scim) ListUsers(g *gin.Context) {
|
||||
companyID, ok := c.parseCompanyID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
result, ok := c.authenticate(g, companyID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
base := scimBaseURL(g, companyID)
|
||||
filter := g.Query("filter")
|
||||
startIndex := parseIntQuery(g, "startIndex", 1)
|
||||
count := parseIntQuery(g, "count", 0)
|
||||
sortBy := g.Query("sortBy")
|
||||
sortOrder := g.Query("sortOrder")
|
||||
|
||||
list, err := c.ScimService.ListUsers(g.Request.Context(), companyID, result.Config, base, filter, startIndex, count, sortBy, sortOrder)
|
||||
if err != nil {
|
||||
c.Logger.Errorw("scim list users: error", "error", err)
|
||||
scimError(g, http.StatusInternalServerError, "failed to list users")
|
||||
return
|
||||
}
|
||||
go c.ScimService.UpdateLastSync(context.Background(), result.Config)
|
||||
scimOK(g, http.StatusOK, list)
|
||||
}
|
||||
|
||||
// GetUser handles GET /scim/v2/:companyID/Users/:userID
|
||||
func (c *Scim) GetUser(g *gin.Context) {
|
||||
companyID, ok := c.parseCompanyID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
result, ok := c.authenticate(g, companyID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
userID, ok := c.parseUserID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
base := scimBaseURL(g, companyID)
|
||||
|
||||
user, err := c.ScimService.GetUser(g.Request.Context(), companyID, result.Config, userID, base)
|
||||
if err != nil {
|
||||
if isNotFound(err) {
|
||||
scimError(g, http.StatusNotFound, "user not found")
|
||||
return
|
||||
}
|
||||
c.Logger.Errorw("scim get user: error", "error", err)
|
||||
scimError(g, http.StatusInternalServerError, "failed to get user")
|
||||
return
|
||||
}
|
||||
scimOK(g, http.StatusOK, user)
|
||||
}
|
||||
|
||||
// CreateUser handles POST /scim/v2/:companyID/Users
|
||||
func (c *Scim) CreateUser(g *gin.Context) {
|
||||
companyID, ok := c.parseCompanyID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
result, ok := c.authenticate(g, companyID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var req service.ScimUser
|
||||
if err := g.ShouldBindJSON(&req); err != nil {
|
||||
scimError(g, http.StatusBadRequest, "invalid request body: "+err.Error(), "invalidValue")
|
||||
return
|
||||
}
|
||||
|
||||
base := scimBaseURL(g, companyID)
|
||||
created, err := c.ScimService.CreateUser(g.Request.Context(), companyID, result.Config, &req, base)
|
||||
if err != nil {
|
||||
if isConflictError(err) {
|
||||
scimError(g, http.StatusConflict, err.Error(), "uniqueness")
|
||||
return
|
||||
}
|
||||
if isValidationError(err) {
|
||||
scimError(g, http.StatusBadRequest, err.Error(), "invalidValue")
|
||||
return
|
||||
}
|
||||
c.Logger.Errorw("scim create user: error", "error", err)
|
||||
scimError(g, http.StatusInternalServerError, "failed to create user")
|
||||
return
|
||||
}
|
||||
go c.ScimService.UpdateLastSync(context.Background(), result.Config)
|
||||
scimOK(g, http.StatusCreated, created)
|
||||
}
|
||||
|
||||
// ReplaceUser handles PUT /scim/v2/:companyID/Users/:userID
|
||||
func (c *Scim) ReplaceUser(g *gin.Context) {
|
||||
companyID, ok := c.parseCompanyID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
result, ok := c.authenticate(g, companyID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
userID, ok := c.parseUserID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var req service.ScimUser
|
||||
if err := g.ShouldBindJSON(&req); err != nil {
|
||||
scimError(g, http.StatusBadRequest, "invalid request body: "+err.Error(), "invalidValue")
|
||||
return
|
||||
}
|
||||
|
||||
base := scimBaseURL(g, companyID)
|
||||
updated, err := c.ScimService.ReplaceUser(g.Request.Context(), companyID, result.Config, userID, &req, base)
|
||||
if err != nil {
|
||||
if isNotFound(err) {
|
||||
scimError(g, http.StatusNotFound, "user not found")
|
||||
return
|
||||
}
|
||||
if isValidationError(err) {
|
||||
scimError(g, http.StatusBadRequest, err.Error(), "invalidValue")
|
||||
return
|
||||
}
|
||||
c.Logger.Errorw("scim replace user: error", "error", err)
|
||||
scimError(g, http.StatusInternalServerError, "failed to replace user")
|
||||
return
|
||||
}
|
||||
go c.ScimService.UpdateLastSync(context.Background(), result.Config)
|
||||
scimOK(g, http.StatusOK, updated)
|
||||
}
|
||||
|
||||
// PatchUser handles PATCH /scim/v2/:companyID/Users/:userID
|
||||
func (c *Scim) PatchUser(g *gin.Context) {
|
||||
companyID, ok := c.parseCompanyID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
result, ok := c.authenticate(g, companyID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
userID, ok := c.parseUserID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var req service.ScimPatchOp
|
||||
if err := g.ShouldBindJSON(&req); err != nil {
|
||||
scimError(g, http.StatusBadRequest, "invalid patch body: "+err.Error(), "invalidValue")
|
||||
return
|
||||
}
|
||||
|
||||
base := scimBaseURL(g, companyID)
|
||||
updated, err := c.ScimService.PatchUser(g.Request.Context(), companyID, result.Config, userID, &req, base)
|
||||
if err != nil {
|
||||
if isNotFound(err) {
|
||||
scimError(g, http.StatusNotFound, "user not found")
|
||||
return
|
||||
}
|
||||
if isValidationError(err) {
|
||||
scimError(g, http.StatusBadRequest, err.Error(), "invalidValue")
|
||||
return
|
||||
}
|
||||
c.Logger.Errorw("scim patch user: error", "error", err)
|
||||
scimError(g, http.StatusInternalServerError, "failed to patch user")
|
||||
return
|
||||
}
|
||||
go c.ScimService.UpdateLastSync(context.Background(), result.Config)
|
||||
scimOK(g, http.StatusOK, updated)
|
||||
}
|
||||
|
||||
// DeleteUser handles DELETE /scim/v2/:companyID/Users/:userID
|
||||
func (c *Scim) DeleteUser(g *gin.Context) {
|
||||
companyID, ok := c.parseCompanyID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
result, ok := c.authenticate(g, companyID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
userID, ok := c.parseUserID(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
err := c.ScimService.DeprovisionUser(g.Request.Context(), companyID, result.Config, userID)
|
||||
if err != nil {
|
||||
if isNotFound(err) {
|
||||
scimError(g, http.StatusNotFound, "user not found")
|
||||
return
|
||||
}
|
||||
c.Logger.Errorw("scim delete user: error", "error", err)
|
||||
scimError(g, http.StatusInternalServerError, "failed to deprovision user")
|
||||
return
|
||||
}
|
||||
go c.ScimService.UpdateLastSync(context.Background(), result.Config)
|
||||
// RFC 7644 §3.6 — successful DELETE returns 204 No Content
|
||||
g.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// isValidationError returns true when the error wraps an errs.ValidationError
|
||||
func isValidationError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
var target errs.ValidationError
|
||||
return errors.As(err, &target)
|
||||
}
|
||||
|
||||
// isSyntaxError returns true when the error wraps an errs.SyntaxError
|
||||
func isSyntaxError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
var target errs.SyntaxError
|
||||
return errors.As(err, &target)
|
||||
}
|
||||
|
||||
// isConflictError returns true when the error wraps an errs.ConflictError
|
||||
func isConflictError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
var target errs.ConflictError
|
||||
return errors.As(err, &target)
|
||||
}
|
||||
|
||||
// parseIntQuery parses a query param as an int, returning defaultVal if absent or invalid.
|
||||
func parseIntQuery(g *gin.Context, key string, defaultVal int) int {
|
||||
raw := g.Query(key)
|
||||
if raw == "" {
|
||||
return defaultVal
|
||||
}
|
||||
v, err := strconv.Atoi(raw)
|
||||
if err != nil || v < 0 {
|
||||
return defaultVal
|
||||
}
|
||||
return v
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
COMPANY_SCIM_CONFIG_TABLE = "company_scim_configs"
|
||||
)
|
||||
|
||||
// CompanyScimConfig holds the SCIM configuration for a company
|
||||
type CompanyScimConfig struct {
|
||||
ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
CreatedAt *time.Time `gorm:"not null;index;"`
|
||||
UpdatedAt *time.Time `gorm:"not null;index;"`
|
||||
CompanyID *uuid.UUID `gorm:"not null;unique;type:uuid;index;"` // one-to-one with company
|
||||
TokenHash string `gorm:"not null;"` // bcrypt hash of the bearer token
|
||||
TokenPrefix string `gorm:"not null;"` // first 8 chars of token for identification
|
||||
Enabled bool `gorm:"not null;default:true"`
|
||||
LastSyncAt *time.Time // nullable: last time SCIM pushed an update
|
||||
|
||||
Company *Company `gorm:"foreignKey:CompanyID"`
|
||||
}
|
||||
|
||||
func (e *CompanyScimConfig) Migrate(db *gorm.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (CompanyScimConfig) TableName() string {
|
||||
return COMPANY_SCIM_CONFIG_TABLE
|
||||
}
|
||||
@@ -21,13 +21,14 @@ type Recipient struct {
|
||||
Phone *string `gorm:";index"`
|
||||
ExtraIdentifier *string `gorm:";index"`
|
||||
|
||||
FirstName string `gorm:";"`
|
||||
LastName string `gorm:";"`
|
||||
Position string `gorm:";"`
|
||||
Department string `gorm:";"`
|
||||
City string `gorm:";"`
|
||||
Country string `gorm:";"`
|
||||
Misc string `gorm:";"`
|
||||
FirstName string `gorm:";"`
|
||||
LastName string `gorm:";"`
|
||||
Position string `gorm:";"`
|
||||
Department string `gorm:";"`
|
||||
City string `gorm:";"`
|
||||
Country string `gorm:";"`
|
||||
Misc string `gorm:";"`
|
||||
ScimUserName string `gorm:";"`
|
||||
|
||||
// can belong to
|
||||
CompanyID *uuid.UUID `gorm:"type:uuid;index;"`
|
||||
|
||||
@@ -94,6 +94,40 @@ func (e ValidationError) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// SyntaxError is returned when a request is structurally invalid (scim invalidSyntax)
|
||||
type SyntaxError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
// NewSyntaxError creates a new syntax error
|
||||
func NewSyntaxError(err error) error {
|
||||
return SyntaxError{
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
// Error returns the syntax error
|
||||
func (e SyntaxError) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// ConflictError is returned when a resource already exists (scim uniqueness conflict)
|
||||
type ConflictError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
// NewConflictError creates a new conflict error
|
||||
func NewConflictError(err error) error {
|
||||
return ConflictError{
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
// Error returns the conflict error
|
||||
func (e ConflictError) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// CustomError is a custom error
|
||||
type CustomError struct {
|
||||
Err error
|
||||
|
||||
+7
-7
@@ -26,10 +26,10 @@ require (
|
||||
github.com/wneessen/go-mail v0.7.2
|
||||
github.com/yeqown/go-qrcode/v2 v2.2.4
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.43.0
|
||||
golang.org/x/mod v0.29.0
|
||||
golang.org/x/net v0.46.0
|
||||
golang.org/x/sync v0.18.0
|
||||
golang.org/x/crypto v0.49.0
|
||||
golang.org/x/mod v0.33.0
|
||||
golang.org/x/net v0.51.0
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/time v0.14.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/driver/sqlite v1.6.0
|
||||
@@ -106,8 +106,8 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/arch v0.9.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
golang.org/x/text v0.30.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
google.golang.org/protobuf v1.36.7 // indirect
|
||||
)
|
||||
|
||||
@@ -251,6 +251,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
@@ -258,6 +260,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
@@ -266,10 +270,14 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -283,6 +291,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@@ -293,6 +303,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -301,6 +313,8 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
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=
|
||||
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
|
||||
|
||||
+1
-1
@@ -321,7 +321,7 @@ func main() {
|
||||
"trusted_ip_header", conf.IPSecurity.TrustedIPHeader,
|
||||
)
|
||||
}
|
||||
adminRouter.Use(middlewares.IPLimiter)
|
||||
|
||||
adminServer := app.NewAdministrationServer(
|
||||
adminRouter,
|
||||
controllers,
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/oapi-codegen/nullable"
|
||||
)
|
||||
|
||||
// CompanyScimConfig is the SCIM configuration for a company
|
||||
type CompanyScimConfig struct {
|
||||
ID nullable.Nullable[uuid.UUID] `json:"id"`
|
||||
CreatedAt *time.Time `json:"createdAt"`
|
||||
UpdatedAt *time.Time `json:"updatedAt"`
|
||||
CompanyID nullable.Nullable[uuid.UUID] `json:"companyID"`
|
||||
TokenPrefix nullable.Nullable[string] `json:"tokenPrefix"` // shown to user for identification
|
||||
Enabled bool `json:"enabled"`
|
||||
LastSyncAt *time.Time `json:"lastSyncAt"`
|
||||
// Token is only set when a new token has just been generated (never persisted directly)
|
||||
Token string `json:"token,omitempty"`
|
||||
}
|
||||
|
||||
// Validate checks if the config is valid
|
||||
func (c *CompanyScimConfig) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToDBMap converts the fields for persistence
|
||||
func (c *CompanyScimConfig) ToDBMap() map[string]any {
|
||||
m := map[string]any{}
|
||||
m["enabled"] = c.Enabled
|
||||
return m
|
||||
}
|
||||
@@ -24,6 +24,7 @@ type Recipient struct {
|
||||
City nullable.Nullable[vo.OptionalString127] `json:"city"`
|
||||
Country nullable.Nullable[vo.OptionalString127] `json:"country"`
|
||||
Misc nullable.Nullable[vo.OptionalString127] `json:"misc"`
|
||||
ScimUserName nullable.Nullable[vo.OptionalString127] `json:"scimUserName"`
|
||||
CompanyID nullable.Nullable[uuid.UUID] `json:"companyID"`
|
||||
|
||||
Company *Company `json:"-"`
|
||||
@@ -178,6 +179,12 @@ func (r *Recipient) ToDBMap() map[string]any {
|
||||
m["misc"] = misc.String()
|
||||
}
|
||||
}
|
||||
if r.ScimUserName.IsSpecified() {
|
||||
m["scim_user_name"] = nil
|
||||
if scimUserName, err := r.ScimUserName.Get(); err == nil {
|
||||
m["scim_user_name"] = scimUserName.String()
|
||||
}
|
||||
}
|
||||
if r.CompanyID.IsSpecified() {
|
||||
if r.CompanyID.IsNull() {
|
||||
m["company_id"] = nil
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/oapi-codegen/nullable"
|
||||
"github.com/phishingclub/phishingclub/database"
|
||||
"github.com/phishingclub/phishingclub/errs"
|
||||
"github.com/phishingclub/phishingclub/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CompanyScimConfig is the repository for company SCIM configuration
|
||||
type CompanyScimConfig struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
// Insert inserts a new company SCIM config row with a pre-computed bcrypt token hash
|
||||
func (r *CompanyScimConfig) Insert(
|
||||
ctx context.Context,
|
||||
config *model.CompanyScimConfig,
|
||||
tokenHash string,
|
||||
) (*uuid.UUID, error) {
|
||||
id := uuid.New()
|
||||
row := map[string]any{}
|
||||
row["id"] = id
|
||||
AddTimestamps(row)
|
||||
|
||||
row["token_hash"] = tokenHash
|
||||
|
||||
if companyID, err := config.CompanyID.Get(); err == nil {
|
||||
row["company_id"] = companyID.String()
|
||||
} else {
|
||||
return nil, fmt.Errorf("company_id is required")
|
||||
}
|
||||
|
||||
if tokenPrefix, err := config.TokenPrefix.Get(); err == nil {
|
||||
row["token_prefix"] = tokenPrefix
|
||||
} else {
|
||||
row["token_prefix"] = ""
|
||||
}
|
||||
|
||||
row["enabled"] = config.Enabled
|
||||
|
||||
res := r.DB.
|
||||
Model(&database.CompanyScimConfig{}).
|
||||
Create(row)
|
||||
|
||||
if res.Error != nil {
|
||||
return nil, errs.Wrap(res.Error)
|
||||
}
|
||||
return &id, nil
|
||||
}
|
||||
|
||||
// GetByCompanyID fetches the SCIM config for a given company
|
||||
func (r *CompanyScimConfig) GetByCompanyID(
|
||||
ctx context.Context,
|
||||
companyID *uuid.UUID,
|
||||
) (*model.CompanyScimConfig, error) {
|
||||
var row database.CompanyScimConfig
|
||||
res := r.DB.
|
||||
Where(
|
||||
fmt.Sprintf(
|
||||
"%s = ?",
|
||||
TableColumn(database.COMPANY_SCIM_CONFIG_TABLE, "company_id"),
|
||||
),
|
||||
companyID.String(),
|
||||
).
|
||||
First(&row)
|
||||
|
||||
if res.Error != nil {
|
||||
return nil, errs.Wrap(res.Error)
|
||||
}
|
||||
return ToCompanyScimConfig(&row), nil
|
||||
}
|
||||
|
||||
// GetByID fetches the SCIM config by its primary key
|
||||
func (r *CompanyScimConfig) GetByID(
|
||||
ctx context.Context,
|
||||
id *uuid.UUID,
|
||||
) (*model.CompanyScimConfig, error) {
|
||||
var row database.CompanyScimConfig
|
||||
res := r.DB.
|
||||
Where(
|
||||
fmt.Sprintf(
|
||||
"%s = ?",
|
||||
TableColumnID(database.COMPANY_SCIM_CONFIG_TABLE),
|
||||
),
|
||||
id.String(),
|
||||
).
|
||||
First(&row)
|
||||
|
||||
if res.Error != nil {
|
||||
return nil, errs.Wrap(res.Error)
|
||||
}
|
||||
return ToCompanyScimConfig(&row), nil
|
||||
}
|
||||
|
||||
// UpdateByID performs a partial update on the SCIM config via ToDBMap
|
||||
func (r *CompanyScimConfig) UpdateByID(
|
||||
ctx context.Context,
|
||||
id *uuid.UUID,
|
||||
config *model.CompanyScimConfig,
|
||||
) error {
|
||||
row := config.ToDBMap()
|
||||
AddUpdatedAt(row)
|
||||
|
||||
res := r.DB.
|
||||
Model(&database.CompanyScimConfig{}).
|
||||
Where(
|
||||
fmt.Sprintf(
|
||||
"%s = ?",
|
||||
TableColumnID(database.COMPANY_SCIM_CONFIG_TABLE),
|
||||
),
|
||||
id.String(),
|
||||
).
|
||||
Updates(row)
|
||||
|
||||
if res.Error != nil {
|
||||
return errs.Wrap(res.Error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateTokenByID replaces the token hash and prefix and bumps updated_at
|
||||
func (r *CompanyScimConfig) UpdateTokenByID(
|
||||
ctx context.Context,
|
||||
id *uuid.UUID,
|
||||
tokenHash string,
|
||||
tokenPrefix string,
|
||||
) error {
|
||||
row := map[string]any{
|
||||
"token_hash": tokenHash,
|
||||
"token_prefix": tokenPrefix,
|
||||
}
|
||||
AddUpdatedAt(row)
|
||||
|
||||
res := r.DB.
|
||||
Model(&database.CompanyScimConfig{}).
|
||||
Where(
|
||||
fmt.Sprintf(
|
||||
"%s = ?",
|
||||
TableColumnID(database.COMPANY_SCIM_CONFIG_TABLE),
|
||||
),
|
||||
id.String(),
|
||||
).
|
||||
Updates(row)
|
||||
|
||||
if res.Error != nil {
|
||||
return errs.Wrap(res.Error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateLastSyncAt sets last_sync_at to the current UTC time for the given config ID
|
||||
func (r *CompanyScimConfig) UpdateLastSyncAt(
|
||||
ctx context.Context,
|
||||
id *uuid.UUID,
|
||||
) error {
|
||||
now := time.Now().UTC()
|
||||
row := map[string]any{
|
||||
"last_sync_at": now,
|
||||
}
|
||||
AddUpdatedAt(row)
|
||||
|
||||
res := r.DB.
|
||||
Model(&database.CompanyScimConfig{}).
|
||||
Where(
|
||||
fmt.Sprintf(
|
||||
"%s = ?",
|
||||
TableColumnID(database.COMPANY_SCIM_CONFIG_TABLE),
|
||||
),
|
||||
id.String(),
|
||||
).
|
||||
Updates(row)
|
||||
|
||||
if res.Error != nil {
|
||||
return errs.Wrap(res.Error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTokenHashByCompanyID fetches only the token hash for the given company,
|
||||
// used during bearer-token verification so the hash never leaks into the model layer
|
||||
func (r *CompanyScimConfig) GetTokenHashByCompanyID(
|
||||
ctx context.Context,
|
||||
companyID *uuid.UUID,
|
||||
) (string, error) {
|
||||
var row database.CompanyScimConfig
|
||||
res := r.DB.
|
||||
Select("token_hash").
|
||||
Where(
|
||||
fmt.Sprintf(
|
||||
"%s = ?",
|
||||
TableColumn(database.COMPANY_SCIM_CONFIG_TABLE, "company_id"),
|
||||
),
|
||||
companyID.String(),
|
||||
).
|
||||
First(&row)
|
||||
|
||||
if res.Error != nil {
|
||||
return "", errs.Wrap(res.Error)
|
||||
}
|
||||
return row.TokenHash, nil
|
||||
}
|
||||
|
||||
// GetWithTokenHashByCompanyID fetches the full config row and the token hash in a
|
||||
// single query, used during bearer-token verification.
|
||||
func (r *CompanyScimConfig) GetWithTokenHashByCompanyID(
|
||||
ctx context.Context,
|
||||
companyID *uuid.UUID,
|
||||
) (*model.CompanyScimConfig, string, error) {
|
||||
var row database.CompanyScimConfig
|
||||
res := r.DB.
|
||||
Where(
|
||||
fmt.Sprintf(
|
||||
"%s = ?",
|
||||
TableColumn(database.COMPANY_SCIM_CONFIG_TABLE, "company_id"),
|
||||
),
|
||||
companyID.String(),
|
||||
).
|
||||
First(&row)
|
||||
|
||||
if res.Error != nil {
|
||||
return nil, "", errs.Wrap(res.Error)
|
||||
}
|
||||
return ToCompanyScimConfig(&row), row.TokenHash, nil
|
||||
}
|
||||
|
||||
// DeleteByCompanyID removes the SCIM config for a given company
|
||||
func (r *CompanyScimConfig) DeleteByCompanyID(
|
||||
ctx context.Context,
|
||||
companyID *uuid.UUID,
|
||||
) error {
|
||||
res := r.DB.
|
||||
Where(
|
||||
fmt.Sprintf(
|
||||
"%s = ?",
|
||||
TableColumn(database.COMPANY_SCIM_CONFIG_TABLE, "company_id"),
|
||||
),
|
||||
companyID.String(),
|
||||
).
|
||||
Delete(&database.CompanyScimConfig{})
|
||||
|
||||
if res.Error != nil {
|
||||
return errs.Wrap(res.Error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToCompanyScimConfig maps a database row to the business model.
|
||||
// the token field is intentionally left empty — it is never read back from storage.
|
||||
func ToCompanyScimConfig(row *database.CompanyScimConfig) *model.CompanyScimConfig {
|
||||
id := nullable.NewNullableWithValue(*row.ID)
|
||||
|
||||
companyID := nullable.NewNullNullable[uuid.UUID]()
|
||||
if row.CompanyID != nil {
|
||||
companyID.Set(*row.CompanyID)
|
||||
}
|
||||
|
||||
tokenPrefix := nullable.NewNullableWithValue(row.TokenPrefix)
|
||||
|
||||
return &model.CompanyScimConfig{
|
||||
ID: id,
|
||||
CreatedAt: row.CreatedAt,
|
||||
UpdatedAt: row.UpdatedAt,
|
||||
CompanyID: companyID,
|
||||
TokenPrefix: tokenPrefix,
|
||||
Enabled: row.Enabled,
|
||||
LastSyncAt: row.LastSyncAt,
|
||||
}
|
||||
}
|
||||
@@ -880,6 +880,9 @@ func ToRecipient(row *database.Recipient) (*model.Recipient, error) {
|
||||
misc := nullable.NewNullableWithValue(
|
||||
*vo.NewOptionalString127Must(row.Misc),
|
||||
)
|
||||
scimUserName := nullable.NewNullableWithValue(
|
||||
*vo.NewOptionalString127Must(row.ScimUserName),
|
||||
)
|
||||
var company *model.Company
|
||||
if row.Company != nil {
|
||||
company = ToCompany(row.Company)
|
||||
@@ -910,6 +913,7 @@ func ToRecipient(row *database.Recipient) (*model.Recipient, error) {
|
||||
City: city,
|
||||
Country: country,
|
||||
Misc: misc,
|
||||
ScimUserName: scimUserName,
|
||||
Company: company,
|
||||
Groups: nullable.NewNullableWithValue(groups),
|
||||
}, nil
|
||||
|
||||
@@ -14,6 +14,32 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// IsRecipientInGroup returns true when the given recipient is a member of the given group.
|
||||
// uses a COUNT query so it is O(1) regardless of group size.
|
||||
func (rg *RecipientGroup) IsRecipientInGroup(
|
||||
ctx context.Context,
|
||||
groupID *uuid.UUID,
|
||||
recipientID *uuid.UUID,
|
||||
) (bool, error) {
|
||||
var count int64
|
||||
res := rg.DB.
|
||||
Model(&database.RecipientGroupRecipient{}).
|
||||
Where(
|
||||
fmt.Sprintf(
|
||||
"%s = ? AND %s = ?",
|
||||
TableColumn(database.RECIPIENT_GROUP_RECIPIENT_TABLE, "recipient_group_id"),
|
||||
TableColumn(database.RECIPIENT_GROUP_RECIPIENT_TABLE, "recipient_id"),
|
||||
),
|
||||
groupID.String(),
|
||||
recipientID.String(),
|
||||
).
|
||||
Count(&count)
|
||||
if res.Error != nil {
|
||||
return false, errs.Wrap(res.Error)
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
var RecipientGroupAllowedColumns = assignTableToColumns(database.RECIPIENT_GROUP_TABLE, []string{
|
||||
"created_at",
|
||||
"updated_at",
|
||||
|
||||
@@ -58,6 +58,7 @@ func initialInstallAndSeed(
|
||||
&database.OAuthProvider{},
|
||||
&database.OAuthState{},
|
||||
&database.MicrosoftDeviceCode{},
|
||||
&database.CompanyScimConfig{},
|
||||
}
|
||||
|
||||
// disable foreign key constraints temporarily for sqlite to allow table recreation
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/google/uuid"
|
||||
"github.com/oapi-codegen/nullable"
|
||||
"github.com/phishingclub/phishingclub/data"
|
||||
"github.com/phishingclub/phishingclub/errs"
|
||||
"github.com/phishingclub/phishingclub/model"
|
||||
"github.com/phishingclub/phishingclub/repository"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CompanyScimConfig is the service for SCIM configuration per company
|
||||
type CompanyScimConfig struct {
|
||||
Common
|
||||
CompanyScimConfigRepository *repository.CompanyScimConfig
|
||||
}
|
||||
|
||||
// generateToken creates a cryptographically random 32-byte token and returns
|
||||
// the hex-encoded plain token and its 8-character prefix
|
||||
func generateScimToken() (plain string, prefix string, err error) {
|
||||
buf := make([]byte, 32)
|
||||
if _, err = rand.Read(buf); err != nil {
|
||||
return "", "", fmt.Errorf("failed to generate random token: %w", err)
|
||||
}
|
||||
plain = hex.EncodeToString(buf)
|
||||
prefix = plain[:8]
|
||||
return plain, prefix, nil
|
||||
}
|
||||
|
||||
// hashScimToken bcrypt-hashes the plain token with cost 12
|
||||
func hashScimToken(plain string) (string, error) {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(plain), 12)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to hash token: %w", err)
|
||||
}
|
||||
return string(hash), nil
|
||||
}
|
||||
|
||||
// GetByCompanyID returns the SCIM config for the given company
|
||||
func (s *CompanyScimConfig) GetByCompanyID(
|
||||
ctx context.Context,
|
||||
session *model.Session,
|
||||
companyID *uuid.UUID,
|
||||
) (*model.CompanyScimConfig, error) {
|
||||
ae := NewAuditEvent("CompanyScimConfig.GetByCompanyID", session)
|
||||
ae.Details["companyID"] = companyID.String()
|
||||
// check permissions
|
||||
isAuthorized, err := IsAuthorized(session, data.PERMISSION_ALLOW_GLOBAL)
|
||||
if err != nil {
|
||||
s.LogAuthError(err)
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
if !isAuthorized {
|
||||
s.AuditLogNotAuthorized(ae)
|
||||
return nil, errs.ErrAuthorizationFailed
|
||||
}
|
||||
// get
|
||||
config, err := s.CompanyScimConfigRepository.GetByCompanyID(ctx, companyID)
|
||||
if err != nil {
|
||||
s.Logger.Errorw("failed to get scim config by company id", "error", err)
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
// no audit on read
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// Upsert creates or updates the SCIM configuration for the given company.
|
||||
// when creating, a new token is generated and returned in plain text (shown once).
|
||||
// when updating, the token is NOT rotated.
|
||||
func (s *CompanyScimConfig) Upsert(
|
||||
ctx context.Context,
|
||||
session *model.Session,
|
||||
companyID *uuid.UUID,
|
||||
enabled bool,
|
||||
) (*model.CompanyScimConfig, error) {
|
||||
ae := NewAuditEvent("CompanyScimConfig.Upsert", session)
|
||||
ae.Details["companyID"] = companyID.String()
|
||||
// check permissions
|
||||
isAuthorized, err := IsAuthorized(session, data.PERMISSION_ALLOW_GLOBAL)
|
||||
if err != nil {
|
||||
s.LogAuthError(err)
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
if !isAuthorized {
|
||||
s.AuditLogNotAuthorized(ae)
|
||||
return nil, errs.ErrAuthorizationFailed
|
||||
}
|
||||
// check if a config already exists for this company
|
||||
existing, err := s.CompanyScimConfigRepository.GetByCompanyID(ctx, companyID)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
s.Logger.Errorw("failed to look up existing scim config", "error", err)
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
if existing != nil {
|
||||
// update enabled and linked group only; do not regenerate the token
|
||||
existing.Enabled = enabled
|
||||
existingID, idErr := existing.ID.Get()
|
||||
if idErr != nil {
|
||||
s.Logger.Errorw("scim config has no id", "error", idErr)
|
||||
return nil, errs.Wrap(idErr)
|
||||
}
|
||||
if err := s.CompanyScimConfigRepository.UpdateByID(ctx, &existingID, existing); err != nil {
|
||||
s.Logger.Errorw("failed to update scim config", "error", err)
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
ae.Details["id"] = existingID.String()
|
||||
ae.Details["action"] = "update"
|
||||
s.AuditLogAuthorized(ae)
|
||||
return existing, nil
|
||||
}
|
||||
// create: generate a fresh token
|
||||
plain, prefix, err := generateScimToken()
|
||||
if err != nil {
|
||||
s.Logger.Errorw("failed to generate scim token", "error", err)
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
tokenHash, err := hashScimToken(plain)
|
||||
if err != nil {
|
||||
s.Logger.Errorw("failed to hash scim token", "error", err)
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
config := &model.CompanyScimConfig{
|
||||
CompanyID: nullable.NewNullableWithValue(*companyID),
|
||||
Enabled: enabled,
|
||||
TokenPrefix: nullable.NewNullableWithValue(prefix),
|
||||
}
|
||||
id, err := s.CompanyScimConfigRepository.Insert(ctx, config, tokenHash)
|
||||
if err != nil {
|
||||
s.Logger.Errorw("failed to insert scim config", "error", err)
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
ae.Details["id"] = id.String()
|
||||
ae.Details["action"] = "insert"
|
||||
s.AuditLogAuthorized(ae)
|
||||
// load the persisted record and attach the plain token for one-time display
|
||||
created, err := s.CompanyScimConfigRepository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
s.Logger.Errorw("failed to fetch newly inserted scim config", "error", err)
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
// set plain token on model for one-time display; it is never persisted
|
||||
created.Token = plain
|
||||
|
||||
return created, nil
|
||||
}
|
||||
|
||||
// RotateToken generates a new token for the existing SCIM config identified by
|
||||
// companyID. the plain token is returned once so the caller can present it to
|
||||
// the user; only the hash is persisted.
|
||||
func (s *CompanyScimConfig) RotateToken(
|
||||
ctx context.Context,
|
||||
session *model.Session,
|
||||
companyID *uuid.UUID,
|
||||
) (*model.CompanyScimConfig, error) {
|
||||
ae := NewAuditEvent("CompanyScimConfig.RotateToken", session)
|
||||
ae.Details["companyID"] = companyID.String()
|
||||
// check permissions
|
||||
isAuthorized, err := IsAuthorized(session, data.PERMISSION_ALLOW_GLOBAL)
|
||||
if err != nil {
|
||||
s.LogAuthError(err)
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
if !isAuthorized {
|
||||
s.AuditLogNotAuthorized(ae)
|
||||
return nil, errs.ErrAuthorizationFailed
|
||||
}
|
||||
// fetch existing config
|
||||
config, err := s.CompanyScimConfigRepository.GetByCompanyID(ctx, companyID)
|
||||
if err != nil {
|
||||
s.Logger.Errorw("failed to get scim config for token rotation", "error", err)
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
configID, idErr := config.ID.Get()
|
||||
if idErr != nil {
|
||||
s.Logger.Errorw("scim config has no id", "error", idErr)
|
||||
return nil, errs.Wrap(idErr)
|
||||
}
|
||||
// generate a new token
|
||||
plain, prefix, err := generateScimToken()
|
||||
if err != nil {
|
||||
s.Logger.Errorw("failed to generate new scim token", "error", err)
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
tokenHash, err := hashScimToken(plain)
|
||||
if err != nil {
|
||||
s.Logger.Errorw("failed to hash new scim token", "error", err)
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
if err := s.CompanyScimConfigRepository.UpdateTokenByID(ctx, &configID, tokenHash, prefix); err != nil {
|
||||
s.Logger.Errorw("failed to update scim token", "error", err)
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
ae.Details["id"] = configID.String()
|
||||
s.AuditLogAuthorized(ae)
|
||||
// reload and return with plain token set for one-time display
|
||||
updated, err := s.CompanyScimConfigRepository.GetByID(ctx, &configID)
|
||||
if err != nil {
|
||||
s.Logger.Errorw("failed to fetch scim config after token rotation", "error", err)
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
// set plain token on model for one-time display; it is never persisted
|
||||
updated.Token = plain
|
||||
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
// DeleteByCompanyID removes the SCIM configuration for the given company
|
||||
func (s *CompanyScimConfig) DeleteByCompanyID(
|
||||
ctx context.Context,
|
||||
session *model.Session,
|
||||
companyID *uuid.UUID,
|
||||
) error {
|
||||
ae := NewAuditEvent("CompanyScimConfig.DeleteByCompanyID", session)
|
||||
ae.Details["companyID"] = companyID.String()
|
||||
// check permissions
|
||||
isAuthorized, err := IsAuthorized(session, data.PERMISSION_ALLOW_GLOBAL)
|
||||
if err != nil {
|
||||
s.LogAuthError(err)
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
if !isAuthorized {
|
||||
s.AuditLogNotAuthorized(ae)
|
||||
return errs.ErrAuthorizationFailed
|
||||
}
|
||||
// delete
|
||||
if err := s.CompanyScimConfigRepository.DeleteByCompanyID(ctx, companyID); err != nil {
|
||||
s.Logger.Errorw("failed to delete scim config", "error", err)
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
s.AuditLogAuthorized(ae)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyToken checks whether the supplied plain token matches the stored bcrypt
|
||||
// hash for the given company. no session or permission check is performed
|
||||
// because this is called by the inbound SCIM handler during bearer-token auth.
|
||||
func (s *CompanyScimConfig) VerifyToken(
|
||||
ctx context.Context,
|
||||
companyID *uuid.UUID,
|
||||
plainToken string,
|
||||
) (bool, *model.CompanyScimConfig, error) {
|
||||
// fetch config and token hash in a single round-trip
|
||||
config, tokenHash, err := s.CompanyScimConfigRepository.GetWithTokenHashByCompanyID(ctx, companyID)
|
||||
if err != nil {
|
||||
s.Logger.Errorw("failed to get scim config for token verification", "error", err)
|
||||
return false, nil, errs.Wrap(err)
|
||||
}
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(tokenHash), []byte(plainToken)); err != nil {
|
||||
if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
|
||||
return false, config, nil
|
||||
}
|
||||
s.Logger.Errorw("failed to compare scim token hash", "error", err)
|
||||
return false, config, errs.Wrap(err)
|
||||
}
|
||||
|
||||
return true, config, nil
|
||||
}
|
||||
@@ -254,6 +254,7 @@ func (r *RecipientGroup) GetAll(
|
||||
r.AuditLogNotAuthorized(ae)
|
||||
return result, errs.ErrAuthorizationFailed
|
||||
}
|
||||
|
||||
// get recipient groups
|
||||
result, err = r.RecipientGroupRepository.GetAll(
|
||||
ctx,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+11
-7
@@ -6,7 +6,7 @@
|
||||
// Argon2 was selected as the winner of the Password Hashing Competition and can
|
||||
// be used to derive cryptographic keys from passwords.
|
||||
//
|
||||
// For a detailed specification of Argon2 see [1].
|
||||
// For a detailed specification of Argon2 see [argon2-specs.pdf].
|
||||
//
|
||||
// If you aren't sure which function you need, use Argon2id (IDKey) and
|
||||
// the parameter recommendations for your scenario.
|
||||
@@ -17,7 +17,7 @@
|
||||
// It uses data-independent memory access, which is preferred for password
|
||||
// hashing and password-based key derivation. Argon2i requires more passes over
|
||||
// memory than Argon2id to protect from trade-off attacks. The recommended
|
||||
// parameters (taken from [2]) for non-interactive operations are time=3 and to
|
||||
// parameters (taken from [RFC 9106 Section 7.3]) for non-interactive operations are time=3 and to
|
||||
// use the maximum available memory.
|
||||
//
|
||||
// # Argon2id
|
||||
@@ -27,11 +27,11 @@
|
||||
// half of the first iteration over the memory and data-dependent memory access
|
||||
// for the rest. Argon2id is side-channel resistant and provides better brute-
|
||||
// force cost savings due to time-memory tradeoffs than Argon2i. The recommended
|
||||
// parameters for non-interactive operations (taken from [2]) are time=1 and to
|
||||
// parameters for non-interactive operations (taken from [RFC 9106 Section 7.3]) are time=1 and to
|
||||
// use the maximum available memory.
|
||||
//
|
||||
// [1] https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf
|
||||
// [2] https://tools.ietf.org/html/draft-irtf-cfrg-argon2-03#section-9.3
|
||||
// [argon2-specs.pdf]: https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf
|
||||
// [RFC 9106 Section 7.3]: https://www.rfc-editor.org/rfc/rfc9106.html#section-7.3
|
||||
package argon2
|
||||
|
||||
import (
|
||||
@@ -59,7 +59,7 @@ const (
|
||||
//
|
||||
// key := argon2.Key([]byte("some password"), salt, 3, 32*1024, 4, 32)
|
||||
//
|
||||
// The draft RFC recommends[2] time=3, and memory=32*1024 is a sensible number.
|
||||
// [RFC 9106 Section 7.3] recommends time=3, and memory=32*1024 as a sensible number.
|
||||
// If using that amount of memory (32 MB) is not possible in some contexts then
|
||||
// the time parameter can be increased to compensate.
|
||||
//
|
||||
@@ -69,6 +69,8 @@ const (
|
||||
// adjusted to the number of available CPUs. The cost parameters should be
|
||||
// increased as memory latency and CPU parallelism increases. Remember to get a
|
||||
// good random salt.
|
||||
//
|
||||
// [RFC 9106 Section 7.3]: https://www.rfc-editor.org/rfc/rfc9106.html#section-7.3
|
||||
func Key(password, salt []byte, time, memory uint32, threads uint8, keyLen uint32) []byte {
|
||||
return deriveKey(argon2i, password, salt, nil, nil, time, memory, threads, keyLen)
|
||||
}
|
||||
@@ -83,7 +85,7 @@ func Key(password, salt []byte, time, memory uint32, threads uint8, keyLen uint3
|
||||
//
|
||||
// key := argon2.IDKey([]byte("some password"), salt, 1, 64*1024, 4, 32)
|
||||
//
|
||||
// The draft RFC recommends[2] time=1, and memory=64*1024 is a sensible number.
|
||||
// [RFC 9106 Section 7.3] recommends time=1, and memory=64*1024 as a sensible number.
|
||||
// If using that amount of memory (64 MB) is not possible in some contexts then
|
||||
// the time parameter can be increased to compensate.
|
||||
//
|
||||
@@ -93,6 +95,8 @@ func Key(password, salt []byte, time, memory uint32, threads uint8, keyLen uint3
|
||||
// adjusted to the numbers of available CPUs. The cost parameters should be
|
||||
// increased as memory latency and CPU parallelism increases. Remember to get a
|
||||
// good random salt.
|
||||
//
|
||||
// [RFC 9106 Section 7.3]: https://www.rfc-editor.org/rfc/rfc9106.html#section-7.3
|
||||
func IDKey(password, salt []byte, time, memory uint32, threads uint8, keyLen uint32) []byte {
|
||||
return deriveKey(argon2id, password, salt, nil, nil, time, memory, threads, keyLen)
|
||||
}
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bcrypt
|
||||
|
||||
import "encoding/base64"
|
||||
|
||||
const alphabet = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
|
||||
var bcEncoding = base64.NewEncoding(alphabet)
|
||||
|
||||
func base64Encode(src []byte) []byte {
|
||||
n := bcEncoding.EncodedLen(len(src))
|
||||
dst := make([]byte, n)
|
||||
bcEncoding.Encode(dst, src)
|
||||
for dst[n-1] == '=' {
|
||||
n--
|
||||
}
|
||||
return dst[:n]
|
||||
}
|
||||
|
||||
func base64Decode(src []byte) ([]byte, error) {
|
||||
numOfEquals := 4 - (len(src) % 4)
|
||||
for i := 0; i < numOfEquals; i++ {
|
||||
src = append(src, '=')
|
||||
}
|
||||
|
||||
dst := make([]byte, bcEncoding.DecodedLen(len(src)))
|
||||
n, err := bcEncoding.Decode(dst, src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dst[:n], nil
|
||||
}
|
||||
+304
@@ -0,0 +1,304 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing
|
||||
// algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf
|
||||
package bcrypt
|
||||
|
||||
// The code is a port of Provos and Mazières's C implementation.
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/crypto/blowfish"
|
||||
)
|
||||
|
||||
const (
|
||||
MinCost int = 4 // the minimum allowable cost as passed in to GenerateFromPassword
|
||||
MaxCost int = 31 // the maximum allowable cost as passed in to GenerateFromPassword
|
||||
DefaultCost int = 10 // the cost that will actually be set if a cost below MinCost is passed into GenerateFromPassword
|
||||
)
|
||||
|
||||
// The error returned from CompareHashAndPassword when a password and hash do
|
||||
// not match.
|
||||
var ErrMismatchedHashAndPassword = errors.New("crypto/bcrypt: hashedPassword is not the hash of the given password")
|
||||
|
||||
// The error returned from CompareHashAndPassword when a hash is too short to
|
||||
// be a bcrypt hash.
|
||||
var ErrHashTooShort = errors.New("crypto/bcrypt: hashedSecret too short to be a bcrypted password")
|
||||
|
||||
// The error returned from CompareHashAndPassword when a hash was created with
|
||||
// a bcrypt algorithm newer than this implementation.
|
||||
type HashVersionTooNewError byte
|
||||
|
||||
func (hv HashVersionTooNewError) Error() string {
|
||||
return fmt.Sprintf("crypto/bcrypt: bcrypt algorithm version '%c' requested is newer than current version '%c'", byte(hv), majorVersion)
|
||||
}
|
||||
|
||||
// The error returned from CompareHashAndPassword when a hash starts with something other than '$'
|
||||
type InvalidHashPrefixError byte
|
||||
|
||||
func (ih InvalidHashPrefixError) Error() string {
|
||||
return fmt.Sprintf("crypto/bcrypt: bcrypt hashes must start with '$', but hashedSecret started with '%c'", byte(ih))
|
||||
}
|
||||
|
||||
type InvalidCostError int
|
||||
|
||||
func (ic InvalidCostError) Error() string {
|
||||
return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed inclusive range %d..%d", int(ic), MinCost, MaxCost)
|
||||
}
|
||||
|
||||
const (
|
||||
majorVersion = '2'
|
||||
minorVersion = 'a'
|
||||
maxSaltSize = 16
|
||||
maxCryptedHashSize = 23
|
||||
encodedSaltSize = 22
|
||||
encodedHashSize = 31
|
||||
minHashSize = 59
|
||||
)
|
||||
|
||||
// magicCipherData is an IV for the 64 Blowfish encryption calls in
|
||||
// bcrypt(). It's the string "OrpheanBeholderScryDoubt" in big-endian bytes.
|
||||
var magicCipherData = []byte{
|
||||
0x4f, 0x72, 0x70, 0x68,
|
||||
0x65, 0x61, 0x6e, 0x42,
|
||||
0x65, 0x68, 0x6f, 0x6c,
|
||||
0x64, 0x65, 0x72, 0x53,
|
||||
0x63, 0x72, 0x79, 0x44,
|
||||
0x6f, 0x75, 0x62, 0x74,
|
||||
}
|
||||
|
||||
type hashed struct {
|
||||
hash []byte
|
||||
salt []byte
|
||||
cost int // allowed range is MinCost to MaxCost
|
||||
major byte
|
||||
minor byte
|
||||
}
|
||||
|
||||
// ErrPasswordTooLong is returned when the password passed to
|
||||
// GenerateFromPassword is too long (i.e. > 72 bytes).
|
||||
var ErrPasswordTooLong = errors.New("bcrypt: password length exceeds 72 bytes")
|
||||
|
||||
// GenerateFromPassword returns the bcrypt hash of the password at the given
|
||||
// cost. If the cost given is less than MinCost, the cost will be set to
|
||||
// DefaultCost, instead. Use CompareHashAndPassword, as defined in this package,
|
||||
// to compare the returned hashed password with its cleartext version.
|
||||
// GenerateFromPassword does not accept passwords longer than 72 bytes, which
|
||||
// is the longest password bcrypt will operate on.
|
||||
func GenerateFromPassword(password []byte, cost int) ([]byte, error) {
|
||||
if len(password) > 72 {
|
||||
return nil, ErrPasswordTooLong
|
||||
}
|
||||
p, err := newFromPassword(password, cost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.Hash(), nil
|
||||
}
|
||||
|
||||
// CompareHashAndPassword compares a bcrypt hashed password with its possible
|
||||
// plaintext equivalent. Returns nil on success, or an error on failure.
|
||||
func CompareHashAndPassword(hashedPassword, password []byte) error {
|
||||
p, err := newFromHash(hashedPassword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
otherHash, err := bcrypt(password, p.cost, p.salt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
otherP := &hashed{otherHash, p.salt, p.cost, p.major, p.minor}
|
||||
if subtle.ConstantTimeCompare(p.Hash(), otherP.Hash()) == 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrMismatchedHashAndPassword
|
||||
}
|
||||
|
||||
// Cost returns the hashing cost used to create the given hashed
|
||||
// password. When, in the future, the hashing cost of a password system needs
|
||||
// to be increased in order to adjust for greater computational power, this
|
||||
// function allows one to establish which passwords need to be updated.
|
||||
func Cost(hashedPassword []byte) (int, error) {
|
||||
p, err := newFromHash(hashedPassword)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return p.cost, nil
|
||||
}
|
||||
|
||||
func newFromPassword(password []byte, cost int) (*hashed, error) {
|
||||
if cost < MinCost {
|
||||
cost = DefaultCost
|
||||
}
|
||||
p := new(hashed)
|
||||
p.major = majorVersion
|
||||
p.minor = minorVersion
|
||||
|
||||
err := checkCost(cost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.cost = cost
|
||||
|
||||
unencodedSalt := make([]byte, maxSaltSize)
|
||||
_, err = io.ReadFull(rand.Reader, unencodedSalt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.salt = base64Encode(unencodedSalt)
|
||||
hash, err := bcrypt(password, p.cost, p.salt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.hash = hash
|
||||
return p, err
|
||||
}
|
||||
|
||||
func newFromHash(hashedSecret []byte) (*hashed, error) {
|
||||
if len(hashedSecret) < minHashSize {
|
||||
return nil, ErrHashTooShort
|
||||
}
|
||||
p := new(hashed)
|
||||
n, err := p.decodeVersion(hashedSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hashedSecret = hashedSecret[n:]
|
||||
n, err = p.decodeCost(hashedSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hashedSecret = hashedSecret[n:]
|
||||
|
||||
// The "+2" is here because we'll have to append at most 2 '=' to the salt
|
||||
// when base64 decoding it in expensiveBlowfishSetup().
|
||||
p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2)
|
||||
copy(p.salt, hashedSecret[:encodedSaltSize])
|
||||
|
||||
hashedSecret = hashedSecret[encodedSaltSize:]
|
||||
p.hash = make([]byte, len(hashedSecret))
|
||||
copy(p.hash, hashedSecret)
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func bcrypt(password []byte, cost int, salt []byte) ([]byte, error) {
|
||||
cipherData := make([]byte, len(magicCipherData))
|
||||
copy(cipherData, magicCipherData)
|
||||
|
||||
c, err := expensiveBlowfishSetup(password, uint32(cost), salt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := 0; i < 24; i += 8 {
|
||||
for j := 0; j < 64; j++ {
|
||||
c.Encrypt(cipherData[i:i+8], cipherData[i:i+8])
|
||||
}
|
||||
}
|
||||
|
||||
// Bug compatibility with C bcrypt implementations. We only encode 23 of
|
||||
// the 24 bytes encrypted.
|
||||
hsh := base64Encode(cipherData[:maxCryptedHashSize])
|
||||
return hsh, nil
|
||||
}
|
||||
|
||||
func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) {
|
||||
csalt, err := base64Decode(salt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Bug compatibility with C bcrypt implementations. They use the trailing
|
||||
// NULL in the key string during expansion.
|
||||
// We copy the key to prevent changing the underlying array.
|
||||
ckey := append(key[:len(key):len(key)], 0)
|
||||
|
||||
c, err := blowfish.NewSaltedCipher(ckey, csalt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var i, rounds uint64
|
||||
rounds = 1 << cost
|
||||
for i = 0; i < rounds; i++ {
|
||||
blowfish.ExpandKey(ckey, c)
|
||||
blowfish.ExpandKey(csalt, c)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (p *hashed) Hash() []byte {
|
||||
arr := make([]byte, 60)
|
||||
arr[0] = '$'
|
||||
arr[1] = p.major
|
||||
n := 2
|
||||
if p.minor != 0 {
|
||||
arr[2] = p.minor
|
||||
n = 3
|
||||
}
|
||||
arr[n] = '$'
|
||||
n++
|
||||
copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost)))
|
||||
n += 2
|
||||
arr[n] = '$'
|
||||
n++
|
||||
copy(arr[n:], p.salt)
|
||||
n += encodedSaltSize
|
||||
copy(arr[n:], p.hash)
|
||||
n += encodedHashSize
|
||||
return arr[:n]
|
||||
}
|
||||
|
||||
func (p *hashed) decodeVersion(sbytes []byte) (int, error) {
|
||||
if sbytes[0] != '$' {
|
||||
return -1, InvalidHashPrefixError(sbytes[0])
|
||||
}
|
||||
if sbytes[1] > majorVersion {
|
||||
return -1, HashVersionTooNewError(sbytes[1])
|
||||
}
|
||||
p.major = sbytes[1]
|
||||
n := 3
|
||||
if sbytes[2] != '$' {
|
||||
p.minor = sbytes[2]
|
||||
n++
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// sbytes should begin where decodeVersion left off.
|
||||
func (p *hashed) decodeCost(sbytes []byte) (int, error) {
|
||||
cost, err := strconv.Atoi(string(sbytes[0:2]))
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
err = checkCost(cost)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
p.cost = cost
|
||||
return 3, nil
|
||||
}
|
||||
|
||||
func (p *hashed) String() string {
|
||||
return fmt.Sprintf("&{hash: %#v, salt: %#v, cost: %d, major: %c, minor: %c}", string(p.hash), p.salt, p.cost, p.major, p.minor)
|
||||
}
|
||||
|
||||
func checkCost(cost int) error {
|
||||
if cost < MinCost || cost > MaxCost {
|
||||
return InvalidCostError(cost)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package blowfish
|
||||
|
||||
// getNextWord returns the next big-endian uint32 value from the byte slice
|
||||
// at the given position in a circular manner, updating the position.
|
||||
func getNextWord(b []byte, pos *int) uint32 {
|
||||
var w uint32
|
||||
j := *pos
|
||||
for i := 0; i < 4; i++ {
|
||||
w = w<<8 | uint32(b[j])
|
||||
j++
|
||||
if j >= len(b) {
|
||||
j = 0
|
||||
}
|
||||
}
|
||||
*pos = j
|
||||
return w
|
||||
}
|
||||
|
||||
// ExpandKey performs a key expansion on the given *Cipher. Specifically, it
|
||||
// performs the Blowfish algorithm's key schedule which sets up the *Cipher's
|
||||
// pi and substitution tables for calls to Encrypt. This is used, primarily,
|
||||
// by the bcrypt package to reuse the Blowfish key schedule during its
|
||||
// set up. It's unlikely that you need to use this directly.
|
||||
func ExpandKey(key []byte, c *Cipher) {
|
||||
j := 0
|
||||
for i := 0; i < 18; i++ {
|
||||
// Using inlined getNextWord for performance.
|
||||
var d uint32
|
||||
for k := 0; k < 4; k++ {
|
||||
d = d<<8 | uint32(key[j])
|
||||
j++
|
||||
if j >= len(key) {
|
||||
j = 0
|
||||
}
|
||||
}
|
||||
c.p[i] ^= d
|
||||
}
|
||||
|
||||
var l, r uint32
|
||||
for i := 0; i < 18; i += 2 {
|
||||
l, r = encryptBlock(l, r, c)
|
||||
c.p[i], c.p[i+1] = l, r
|
||||
}
|
||||
|
||||
for i := 0; i < 256; i += 2 {
|
||||
l, r = encryptBlock(l, r, c)
|
||||
c.s0[i], c.s0[i+1] = l, r
|
||||
}
|
||||
for i := 0; i < 256; i += 2 {
|
||||
l, r = encryptBlock(l, r, c)
|
||||
c.s1[i], c.s1[i+1] = l, r
|
||||
}
|
||||
for i := 0; i < 256; i += 2 {
|
||||
l, r = encryptBlock(l, r, c)
|
||||
c.s2[i], c.s2[i+1] = l, r
|
||||
}
|
||||
for i := 0; i < 256; i += 2 {
|
||||
l, r = encryptBlock(l, r, c)
|
||||
c.s3[i], c.s3[i+1] = l, r
|
||||
}
|
||||
}
|
||||
|
||||
// This is similar to ExpandKey, but folds the salt during the key
|
||||
// schedule. While ExpandKey is essentially expandKeyWithSalt with an all-zero
|
||||
// salt passed in, reusing ExpandKey turns out to be a place of inefficiency
|
||||
// and specializing it here is useful.
|
||||
func expandKeyWithSalt(key []byte, salt []byte, c *Cipher) {
|
||||
j := 0
|
||||
for i := 0; i < 18; i++ {
|
||||
c.p[i] ^= getNextWord(key, &j)
|
||||
}
|
||||
|
||||
j = 0
|
||||
var l, r uint32
|
||||
for i := 0; i < 18; i += 2 {
|
||||
l ^= getNextWord(salt, &j)
|
||||
r ^= getNextWord(salt, &j)
|
||||
l, r = encryptBlock(l, r, c)
|
||||
c.p[i], c.p[i+1] = l, r
|
||||
}
|
||||
|
||||
for i := 0; i < 256; i += 2 {
|
||||
l ^= getNextWord(salt, &j)
|
||||
r ^= getNextWord(salt, &j)
|
||||
l, r = encryptBlock(l, r, c)
|
||||
c.s0[i], c.s0[i+1] = l, r
|
||||
}
|
||||
|
||||
for i := 0; i < 256; i += 2 {
|
||||
l ^= getNextWord(salt, &j)
|
||||
r ^= getNextWord(salt, &j)
|
||||
l, r = encryptBlock(l, r, c)
|
||||
c.s1[i], c.s1[i+1] = l, r
|
||||
}
|
||||
|
||||
for i := 0; i < 256; i += 2 {
|
||||
l ^= getNextWord(salt, &j)
|
||||
r ^= getNextWord(salt, &j)
|
||||
l, r = encryptBlock(l, r, c)
|
||||
c.s2[i], c.s2[i+1] = l, r
|
||||
}
|
||||
|
||||
for i := 0; i < 256; i += 2 {
|
||||
l ^= getNextWord(salt, &j)
|
||||
r ^= getNextWord(salt, &j)
|
||||
l, r = encryptBlock(l, r, c)
|
||||
c.s3[i], c.s3[i+1] = l, r
|
||||
}
|
||||
}
|
||||
|
||||
func encryptBlock(l, r uint32, c *Cipher) (uint32, uint32) {
|
||||
xl, xr := l, r
|
||||
xl ^= c.p[0]
|
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[1]
|
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[2]
|
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[3]
|
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[4]
|
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[5]
|
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[6]
|
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[7]
|
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[8]
|
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[9]
|
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[10]
|
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[11]
|
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[12]
|
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[13]
|
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[14]
|
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[15]
|
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[16]
|
||||
xr ^= c.p[17]
|
||||
return xr, xl
|
||||
}
|
||||
|
||||
func decryptBlock(l, r uint32, c *Cipher) (uint32, uint32) {
|
||||
xl, xr := l, r
|
||||
xl ^= c.p[17]
|
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[16]
|
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[15]
|
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[14]
|
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[13]
|
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[12]
|
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[11]
|
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[10]
|
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[9]
|
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[8]
|
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[7]
|
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[6]
|
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[5]
|
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[4]
|
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[3]
|
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[2]
|
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[1]
|
||||
xr ^= c.p[0]
|
||||
return xr, xl
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package blowfish implements Bruce Schneier's Blowfish encryption algorithm.
|
||||
//
|
||||
// Blowfish is a legacy cipher and its short block size makes it vulnerable to
|
||||
// birthday bound attacks (see https://sweet32.info). It should only be used
|
||||
// where compatibility with legacy systems, not security, is the goal.
|
||||
//
|
||||
// Deprecated: any new system should use AES (from crypto/aes, if necessary in
|
||||
// an AEAD mode like crypto/cipher.NewGCM) or XChaCha20-Poly1305 (from
|
||||
// golang.org/x/crypto/chacha20poly1305).
|
||||
package blowfish
|
||||
|
||||
// The code is a port of Bruce Schneier's C implementation.
|
||||
// See https://www.schneier.com/blowfish.html.
|
||||
|
||||
import "strconv"
|
||||
|
||||
// The Blowfish block size in bytes.
|
||||
const BlockSize = 8
|
||||
|
||||
// A Cipher is an instance of Blowfish encryption using a particular key.
|
||||
type Cipher struct {
|
||||
p [18]uint32
|
||||
s0, s1, s2, s3 [256]uint32
|
||||
}
|
||||
|
||||
type KeySizeError int
|
||||
|
||||
func (k KeySizeError) Error() string {
|
||||
return "crypto/blowfish: invalid key size " + strconv.Itoa(int(k))
|
||||
}
|
||||
|
||||
// NewCipher creates and returns a Cipher.
|
||||
// The key argument should be the Blowfish key, from 1 to 56 bytes.
|
||||
func NewCipher(key []byte) (*Cipher, error) {
|
||||
var result Cipher
|
||||
if k := len(key); k < 1 || k > 56 {
|
||||
return nil, KeySizeError(k)
|
||||
}
|
||||
initCipher(&result)
|
||||
ExpandKey(key, &result)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// NewSaltedCipher creates a returns a Cipher that folds a salt into its key
|
||||
// schedule. For most purposes, NewCipher, instead of NewSaltedCipher, is
|
||||
// sufficient and desirable. For bcrypt compatibility, the key can be over 56
|
||||
// bytes.
|
||||
func NewSaltedCipher(key, salt []byte) (*Cipher, error) {
|
||||
if len(salt) == 0 {
|
||||
return NewCipher(key)
|
||||
}
|
||||
var result Cipher
|
||||
if k := len(key); k < 1 {
|
||||
return nil, KeySizeError(k)
|
||||
}
|
||||
initCipher(&result)
|
||||
expandKeyWithSalt(key, salt, &result)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// BlockSize returns the Blowfish block size, 8 bytes.
|
||||
// It is necessary to satisfy the Block interface in the
|
||||
// package "crypto/cipher".
|
||||
func (c *Cipher) BlockSize() int { return BlockSize }
|
||||
|
||||
// Encrypt encrypts the 8-byte buffer src using the key k
|
||||
// and stores the result in dst.
|
||||
// Note that for amounts of data larger than a block,
|
||||
// it is not safe to just call Encrypt on successive blocks;
|
||||
// instead, use an encryption mode like CBC (see crypto/cipher/cbc.go).
|
||||
func (c *Cipher) Encrypt(dst, src []byte) {
|
||||
l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])
|
||||
r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7])
|
||||
l, r = encryptBlock(l, r, c)
|
||||
dst[0], dst[1], dst[2], dst[3] = byte(l>>24), byte(l>>16), byte(l>>8), byte(l)
|
||||
dst[4], dst[5], dst[6], dst[7] = byte(r>>24), byte(r>>16), byte(r>>8), byte(r)
|
||||
}
|
||||
|
||||
// Decrypt decrypts the 8-byte buffer src using the key k
|
||||
// and stores the result in dst.
|
||||
func (c *Cipher) Decrypt(dst, src []byte) {
|
||||
l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])
|
||||
r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7])
|
||||
l, r = decryptBlock(l, r, c)
|
||||
dst[0], dst[1], dst[2], dst[3] = byte(l>>24), byte(l>>16), byte(l>>8), byte(l)
|
||||
dst[4], dst[5], dst[6], dst[7] = byte(r>>24), byte(r>>16), byte(r>>8), byte(r)
|
||||
}
|
||||
|
||||
func initCipher(c *Cipher) {
|
||||
copy(c.p[0:], p[0:])
|
||||
copy(c.s0[0:], s0[0:])
|
||||
copy(c.s1[0:], s1[0:])
|
||||
copy(c.s2[0:], s2[0:])
|
||||
copy(c.s3[0:], s3[0:])
|
||||
}
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// The startup permutation array and substitution boxes.
|
||||
// They are the hexadecimal digits of PI; see:
|
||||
// https://www.schneier.com/code/constants.txt.
|
||||
|
||||
package blowfish
|
||||
|
||||
var s0 = [256]uint32{
|
||||
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96,
|
||||
0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
|
||||
0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658,
|
||||
0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
|
||||
0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e,
|
||||
0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
|
||||
0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6,
|
||||
0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
|
||||
0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c,
|
||||
0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
|
||||
0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1,
|
||||
0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
|
||||
0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a,
|
||||
0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
|
||||
0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176,
|
||||
0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
|
||||
0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706,
|
||||
0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
|
||||
0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b,
|
||||
0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
|
||||
0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c,
|
||||
0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
|
||||
0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a,
|
||||
0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
|
||||
0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760,
|
||||
0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
|
||||
0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8,
|
||||
0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
|
||||
0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33,
|
||||
0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
|
||||
0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0,
|
||||
0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
|
||||
0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777,
|
||||
0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
|
||||
0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705,
|
||||
0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
|
||||
0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e,
|
||||
0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
|
||||
0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9,
|
||||
0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
|
||||
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f,
|
||||
0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
|
||||
0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a,
|
||||
}
|
||||
|
||||
var s1 = [256]uint32{
|
||||
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d,
|
||||
0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
|
||||
0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65,
|
||||
0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
|
||||
0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9,
|
||||
0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
|
||||
0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d,
|
||||
0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
|
||||
0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc,
|
||||
0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
|
||||
0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908,
|
||||
0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
|
||||
0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124,
|
||||
0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
|
||||
0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908,
|
||||
0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
|
||||
0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b,
|
||||
0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
|
||||
0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa,
|
||||
0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
|
||||
0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d,
|
||||
0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
|
||||
0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5,
|
||||
0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
|
||||
0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96,
|
||||
0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
|
||||
0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca,
|
||||
0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
|
||||
0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77,
|
||||
0x11ed935f, 0x16681281, 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
|
||||
0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054,
|
||||
0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
|
||||
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea,
|
||||
0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
|
||||
0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646,
|
||||
0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
|
||||
0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea,
|
||||
0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
|
||||
0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e,
|
||||
0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
|
||||
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd,
|
||||
0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
|
||||
0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7,
|
||||
}
|
||||
|
||||
var s2 = [256]uint32{
|
||||
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7,
|
||||
0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
|
||||
0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af,
|
||||
0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
|
||||
0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4,
|
||||
0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
|
||||
0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec,
|
||||
0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
|
||||
0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332,
|
||||
0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
|
||||
0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58,
|
||||
0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
|
||||
0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22,
|
||||
0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
|
||||
0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60,
|
||||
0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
|
||||
0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99,
|
||||
0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
|
||||
0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74,
|
||||
0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
|
||||
0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3,
|
||||
0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
|
||||
0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979,
|
||||
0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
|
||||
0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa,
|
||||
0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
|
||||
0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086,
|
||||
0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
|
||||
0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24,
|
||||
0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
|
||||
0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84,
|
||||
0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
|
||||
0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09,
|
||||
0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
|
||||
0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe,
|
||||
0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
|
||||
0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0,
|
||||
0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
|
||||
0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188,
|
||||
0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
|
||||
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8,
|
||||
0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
|
||||
0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0,
|
||||
}
|
||||
|
||||
var s3 = [256]uint32{
|
||||
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742,
|
||||
0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
|
||||
0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79,
|
||||
0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
|
||||
0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a,
|
||||
0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
|
||||
0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1,
|
||||
0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
|
||||
0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797,
|
||||
0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
|
||||
0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6,
|
||||
0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
|
||||
0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba,
|
||||
0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
|
||||
0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5,
|
||||
0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
|
||||
0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce,
|
||||
0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
|
||||
0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd,
|
||||
0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
|
||||
0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb,
|
||||
0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
|
||||
0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc,
|
||||
0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
|
||||
0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc,
|
||||
0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
|
||||
0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a,
|
||||
0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
|
||||
0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a,
|
||||
0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
|
||||
0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b,
|
||||
0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
|
||||
0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e,
|
||||
0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
|
||||
0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623,
|
||||
0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
|
||||
0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a,
|
||||
0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
|
||||
0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3,
|
||||
0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
|
||||
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c,
|
||||
0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
|
||||
0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6,
|
||||
}
|
||||
|
||||
var p = [18]uint32{
|
||||
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0,
|
||||
0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
|
||||
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b,
|
||||
}
|
||||
+1
-1
@@ -29,7 +29,7 @@ loop:
|
||||
MOVD $NUM_ROUNDS, R21
|
||||
VLD1 (R11), [V30.S4, V31.S4]
|
||||
|
||||
// load contants
|
||||
// load constants
|
||||
// VLD4R (R10), [V0.S4, V1.S4, V2.S4, V3.S4]
|
||||
WORD $0x4D60E940
|
||||
|
||||
|
||||
+3
@@ -38,6 +38,9 @@ type chacha20poly1305 struct {
|
||||
|
||||
// New returns a ChaCha20-Poly1305 AEAD that uses the given 256-bit key.
|
||||
func New(key []byte) (cipher.AEAD, error) {
|
||||
if fips140Enforced() {
|
||||
return nil, errors.New("chacha20poly1305: use of ChaCha20Poly1305 is not allowed in FIPS 140-only mode")
|
||||
}
|
||||
if len(key) != KeySize {
|
||||
return nil, errors.New("chacha20poly1305: bad key length")
|
||||
}
|
||||
|
||||
+8
-2
@@ -56,7 +56,10 @@ func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []
|
||||
|
||||
ret, out := sliceForAppend(dst, len(plaintext)+16)
|
||||
if alias.InexactOverlap(out, plaintext) {
|
||||
panic("chacha20poly1305: invalid buffer overlap")
|
||||
panic("chacha20poly1305: invalid buffer overlap of output and input")
|
||||
}
|
||||
if alias.AnyOverlap(out, additionalData) {
|
||||
panic("chacha20poly1305: invalid buffer overlap of output and additional data")
|
||||
}
|
||||
chacha20Poly1305Seal(out[:], state[:], plaintext, additionalData)
|
||||
return ret
|
||||
@@ -73,7 +76,10 @@ func (c *chacha20poly1305) open(dst, nonce, ciphertext, additionalData []byte) (
|
||||
ciphertext = ciphertext[:len(ciphertext)-16]
|
||||
ret, out := sliceForAppend(dst, len(ciphertext))
|
||||
if alias.InexactOverlap(out, ciphertext) {
|
||||
panic("chacha20poly1305: invalid buffer overlap")
|
||||
panic("chacha20poly1305: invalid buffer overlap of output and input")
|
||||
}
|
||||
if alias.AnyOverlap(out, additionalData) {
|
||||
panic("chacha20poly1305: invalid buffer overlap of output and additional data")
|
||||
}
|
||||
if !chacha20Poly1305Open(out, state[:], ciphertext, additionalData) {
|
||||
for i := range out {
|
||||
|
||||
+8
-2
@@ -31,7 +31,10 @@ func (c *chacha20poly1305) sealGeneric(dst, nonce, plaintext, additionalData []b
|
||||
ret, out := sliceForAppend(dst, len(plaintext)+poly1305.TagSize)
|
||||
ciphertext, tag := out[:len(plaintext)], out[len(plaintext):]
|
||||
if alias.InexactOverlap(out, plaintext) {
|
||||
panic("chacha20poly1305: invalid buffer overlap")
|
||||
panic("chacha20poly1305: invalid buffer overlap of output and input")
|
||||
}
|
||||
if alias.AnyOverlap(out, additionalData) {
|
||||
panic("chacha20poly1305: invalid buffer overlap of output and additional data")
|
||||
}
|
||||
|
||||
var polyKey [32]byte
|
||||
@@ -67,7 +70,10 @@ func (c *chacha20poly1305) openGeneric(dst, nonce, ciphertext, additionalData []
|
||||
|
||||
ret, out := sliceForAppend(dst, len(ciphertext))
|
||||
if alias.InexactOverlap(out, ciphertext) {
|
||||
panic("chacha20poly1305: invalid buffer overlap")
|
||||
panic("chacha20poly1305: invalid buffer overlap of output and input")
|
||||
}
|
||||
if alias.AnyOverlap(out, additionalData) {
|
||||
panic("chacha20poly1305: invalid buffer overlap of output and additional data")
|
||||
}
|
||||
if !p.Verify(tag) {
|
||||
for i := range out {
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !go1.26
|
||||
|
||||
package chacha20poly1305
|
||||
|
||||
func fips140Enforced() bool { return false }
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.26
|
||||
|
||||
package chacha20poly1305
|
||||
|
||||
import "crypto/fips140"
|
||||
|
||||
func fips140Enforced() bool { return fips140.Enforced() }
|
||||
+3
@@ -22,6 +22,9 @@ type xchacha20poly1305 struct {
|
||||
// preferred when nonce uniqueness cannot be trivially ensured, or whenever
|
||||
// nonces are randomly generated.
|
||||
func NewX(key []byte) (cipher.AEAD, error) {
|
||||
if fips140Enforced() {
|
||||
return nil, errors.New("chacha20poly1305: use of ChaCha20Poly1305 is not allowed in FIPS 140-only mode")
|
||||
}
|
||||
if len(key) != KeySize {
|
||||
return nil, errors.New("chacha20poly1305: bad key length")
|
||||
}
|
||||
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package sha3 implements the SHA-3 fixed-output-length hash functions and
|
||||
// the SHAKE variable-output-length hash functions defined by FIPS-202.
|
||||
//
|
||||
// All types in this package also implement [encoding.BinaryMarshaler],
|
||||
// [encoding.BinaryAppender] and [encoding.BinaryUnmarshaler] to marshal and
|
||||
// unmarshal the internal state of the hash.
|
||||
//
|
||||
// Both types of hash function use the "sponge" construction and the Keccak
|
||||
// permutation. For a detailed specification see http://keccak.noekeon.org/
|
||||
//
|
||||
// # Guidance
|
||||
//
|
||||
// If you aren't sure what function you need, use SHAKE256 with at least 64
|
||||
// bytes of output. The SHAKE instances are faster than the SHA3 instances;
|
||||
// the latter have to allocate memory to conform to the hash.Hash interface.
|
||||
//
|
||||
// If you need a secret-key MAC (message authentication code), prepend the
|
||||
// secret key to the input, hash with SHAKE256 and read at least 32 bytes of
|
||||
// output.
|
||||
//
|
||||
// # Security strengths
|
||||
//
|
||||
// The SHA3-x (x equals 224, 256, 384, or 512) functions have a security
|
||||
// strength against preimage attacks of x bits. Since they only produce "x"
|
||||
// bits of output, their collision-resistance is only "x/2" bits.
|
||||
//
|
||||
// The SHAKE-256 and -128 functions have a generic security strength of 256 and
|
||||
// 128 bits against all attacks, provided that at least 2x bits of their output
|
||||
// is used. Requesting more than 64 or 32 bytes of output, respectively, does
|
||||
// not increase the collision-resistance of the SHAKE functions.
|
||||
//
|
||||
// # The sponge construction
|
||||
//
|
||||
// A sponge builds a pseudo-random function from a public pseudo-random
|
||||
// permutation, by applying the permutation to a state of "rate + capacity"
|
||||
// bytes, but hiding "capacity" of the bytes.
|
||||
//
|
||||
// A sponge starts out with a zero state. To hash an input using a sponge, up
|
||||
// to "rate" bytes of the input are XORed into the sponge's state. The sponge
|
||||
// is then "full" and the permutation is applied to "empty" it. This process is
|
||||
// repeated until all the input has been "absorbed". The input is then padded.
|
||||
// The digest is "squeezed" from the sponge in the same way, except that output
|
||||
// is copied out instead of input being XORed in.
|
||||
//
|
||||
// A sponge is parameterized by its generic security strength, which is equal
|
||||
// to half its capacity; capacity + rate is equal to the permutation's width.
|
||||
// Since the KeccakF-1600 permutation is 1600 bits (200 bytes) wide, this means
|
||||
// that the security strength of a sponge instance is equal to (1600 - bitrate) / 2.
|
||||
//
|
||||
// # Recommendations
|
||||
//
|
||||
// The SHAKE functions are recommended for most new uses. They can produce
|
||||
// output of arbitrary length. SHAKE256, with an output length of at least
|
||||
// 64 bytes, provides 256-bit security against all attacks. The Keccak team
|
||||
// recommends it for most applications upgrading from SHA2-512. (NIST chose a
|
||||
// much stronger, but much slower, sponge instance for SHA3-512.)
|
||||
//
|
||||
// The SHA-3 functions are "drop-in" replacements for the SHA-2 functions.
|
||||
// They produce output of the same length, with the same security strengths
|
||||
// against all attacks. This means, in particular, that SHA3-256 only has
|
||||
// 128-bit collision resistance, because its output length is 32 bytes.
|
||||
package sha3
|
||||
+50
-83
@@ -2,127 +2,94 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package sha3 implements the SHA-3 hash algorithms and the SHAKE extendable
|
||||
// output functions defined in FIPS 202.
|
||||
//
|
||||
// Most of this package is a wrapper around the crypto/sha3 package in the
|
||||
// standard library. The only exception is the legacy Keccak hash functions.
|
||||
package sha3
|
||||
|
||||
// This file provides functions for creating instances of the SHA-3
|
||||
// and SHAKE hash functions, as well as utility functions for hashing
|
||||
// bytes.
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/sha3"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// New224 creates a new SHA3-224 hash.
|
||||
// Its generic security strength is 224 bits against preimage attacks,
|
||||
// and 112 bits against collision attacks.
|
||||
//
|
||||
// It is a wrapper for the [sha3.New224] function in the standard library.
|
||||
//
|
||||
//go:fix inline
|
||||
func New224() hash.Hash {
|
||||
return new224()
|
||||
return sha3.New224()
|
||||
}
|
||||
|
||||
// New256 creates a new SHA3-256 hash.
|
||||
// Its generic security strength is 256 bits against preimage attacks,
|
||||
// and 128 bits against collision attacks.
|
||||
//
|
||||
// It is a wrapper for the [sha3.New256] function in the standard library.
|
||||
//
|
||||
//go:fix inline
|
||||
func New256() hash.Hash {
|
||||
return new256()
|
||||
return sha3.New256()
|
||||
}
|
||||
|
||||
// New384 creates a new SHA3-384 hash.
|
||||
// Its generic security strength is 384 bits against preimage attacks,
|
||||
// and 192 bits against collision attacks.
|
||||
//
|
||||
// It is a wrapper for the [sha3.New384] function in the standard library.
|
||||
//
|
||||
//go:fix inline
|
||||
func New384() hash.Hash {
|
||||
return new384()
|
||||
return sha3.New384()
|
||||
}
|
||||
|
||||
// New512 creates a new SHA3-512 hash.
|
||||
// Its generic security strength is 512 bits against preimage attacks,
|
||||
// and 256 bits against collision attacks.
|
||||
//
|
||||
// It is a wrapper for the [sha3.New512] function in the standard library.
|
||||
//
|
||||
//go:fix inline
|
||||
func New512() hash.Hash {
|
||||
return new512()
|
||||
}
|
||||
|
||||
func init() {
|
||||
crypto.RegisterHash(crypto.SHA3_224, New224)
|
||||
crypto.RegisterHash(crypto.SHA3_256, New256)
|
||||
crypto.RegisterHash(crypto.SHA3_384, New384)
|
||||
crypto.RegisterHash(crypto.SHA3_512, New512)
|
||||
}
|
||||
|
||||
const (
|
||||
dsbyteSHA3 = 0b00000110
|
||||
dsbyteKeccak = 0b00000001
|
||||
dsbyteShake = 0b00011111
|
||||
dsbyteCShake = 0b00000100
|
||||
|
||||
// rateK[c] is the rate in bytes for Keccak[c] where c is the capacity in
|
||||
// bits. Given the sponge size is 1600 bits, the rate is 1600 - c bits.
|
||||
rateK256 = (1600 - 256) / 8
|
||||
rateK448 = (1600 - 448) / 8
|
||||
rateK512 = (1600 - 512) / 8
|
||||
rateK768 = (1600 - 768) / 8
|
||||
rateK1024 = (1600 - 1024) / 8
|
||||
)
|
||||
|
||||
func new224Generic() *state {
|
||||
return &state{rate: rateK448, outputLen: 28, dsbyte: dsbyteSHA3}
|
||||
}
|
||||
|
||||
func new256Generic() *state {
|
||||
return &state{rate: rateK512, outputLen: 32, dsbyte: dsbyteSHA3}
|
||||
}
|
||||
|
||||
func new384Generic() *state {
|
||||
return &state{rate: rateK768, outputLen: 48, dsbyte: dsbyteSHA3}
|
||||
}
|
||||
|
||||
func new512Generic() *state {
|
||||
return &state{rate: rateK1024, outputLen: 64, dsbyte: dsbyteSHA3}
|
||||
}
|
||||
|
||||
// NewLegacyKeccak256 creates a new Keccak-256 hash.
|
||||
//
|
||||
// Only use this function if you require compatibility with an existing cryptosystem
|
||||
// that uses non-standard padding. All other users should use New256 instead.
|
||||
func NewLegacyKeccak256() hash.Hash {
|
||||
return &state{rate: rateK512, outputLen: 32, dsbyte: dsbyteKeccak}
|
||||
}
|
||||
|
||||
// NewLegacyKeccak512 creates a new Keccak-512 hash.
|
||||
//
|
||||
// Only use this function if you require compatibility with an existing cryptosystem
|
||||
// that uses non-standard padding. All other users should use New512 instead.
|
||||
func NewLegacyKeccak512() hash.Hash {
|
||||
return &state{rate: rateK1024, outputLen: 64, dsbyte: dsbyteKeccak}
|
||||
return sha3.New512()
|
||||
}
|
||||
|
||||
// Sum224 returns the SHA3-224 digest of the data.
|
||||
func Sum224(data []byte) (digest [28]byte) {
|
||||
h := New224()
|
||||
h.Write(data)
|
||||
h.Sum(digest[:0])
|
||||
return
|
||||
//
|
||||
// It is a wrapper for the [sha3.Sum224] function in the standard library.
|
||||
//
|
||||
//go:fix inline
|
||||
func Sum224(data []byte) [28]byte {
|
||||
return sha3.Sum224(data)
|
||||
}
|
||||
|
||||
// Sum256 returns the SHA3-256 digest of the data.
|
||||
func Sum256(data []byte) (digest [32]byte) {
|
||||
h := New256()
|
||||
h.Write(data)
|
||||
h.Sum(digest[:0])
|
||||
return
|
||||
//
|
||||
// It is a wrapper for the [sha3.Sum256] function in the standard library.
|
||||
//
|
||||
//go:fix inline
|
||||
func Sum256(data []byte) [32]byte {
|
||||
return sha3.Sum256(data)
|
||||
}
|
||||
|
||||
// Sum384 returns the SHA3-384 digest of the data.
|
||||
func Sum384(data []byte) (digest [48]byte) {
|
||||
h := New384()
|
||||
h.Write(data)
|
||||
h.Sum(digest[:0])
|
||||
return
|
||||
//
|
||||
// It is a wrapper for the [sha3.Sum384] function in the standard library.
|
||||
//
|
||||
//go:fix inline
|
||||
func Sum384(data []byte) [48]byte {
|
||||
return sha3.Sum384(data)
|
||||
}
|
||||
|
||||
// Sum512 returns the SHA3-512 digest of the data.
|
||||
func Sum512(data []byte) (digest [64]byte) {
|
||||
h := New512()
|
||||
h.Write(data)
|
||||
h.Sum(digest[:0])
|
||||
return
|
||||
//
|
||||
// It is a wrapper for the [sha3.Sum512] function in the standard library.
|
||||
//
|
||||
//go:fix inline
|
||||
func Sum512(data []byte) [64]byte {
|
||||
return sha3.Sum512(data)
|
||||
}
|
||||
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !gc || purego || !s390x
|
||||
|
||||
package sha3
|
||||
|
||||
func new224() *state {
|
||||
return new224Generic()
|
||||
}
|
||||
|
||||
func new256() *state {
|
||||
return new256Generic()
|
||||
}
|
||||
|
||||
func new384() *state {
|
||||
return new384Generic()
|
||||
}
|
||||
|
||||
func new512() *state {
|
||||
return new512Generic()
|
||||
}
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build amd64 && !purego && gc
|
||||
|
||||
package sha3
|
||||
|
||||
// This function is implemented in keccakf_amd64.s.
|
||||
|
||||
//go:noescape
|
||||
|
||||
func keccakF1600(a *[25]uint64)
|
||||
-5419
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+34
-15
@@ -4,15 +4,46 @@
|
||||
|
||||
package sha3
|
||||
|
||||
// This implementation is only used for NewLegacyKeccak256 and
|
||||
// NewLegacyKeccak512, which are not implemented by crypto/sha3.
|
||||
// All other functions in this package are wrappers around crypto/sha3.
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"hash"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/cpu"
|
||||
)
|
||||
|
||||
const (
|
||||
dsbyteKeccak = 0b00000001
|
||||
|
||||
// rateK[c] is the rate in bytes for Keccak[c] where c is the capacity in
|
||||
// bits. Given the sponge size is 1600 bits, the rate is 1600 - c bits.
|
||||
rateK256 = (1600 - 256) / 8
|
||||
rateK512 = (1600 - 512) / 8
|
||||
rateK1024 = (1600 - 1024) / 8
|
||||
)
|
||||
|
||||
// NewLegacyKeccak256 creates a new Keccak-256 hash.
|
||||
//
|
||||
// Only use this function if you require compatibility with an existing cryptosystem
|
||||
// that uses non-standard padding. All other users should use New256 instead.
|
||||
func NewLegacyKeccak256() hash.Hash {
|
||||
return &state{rate: rateK512, outputLen: 32, dsbyte: dsbyteKeccak}
|
||||
}
|
||||
|
||||
// NewLegacyKeccak512 creates a new Keccak-512 hash.
|
||||
//
|
||||
// Only use this function if you require compatibility with an existing cryptosystem
|
||||
// that uses non-standard padding. All other users should use New512 instead.
|
||||
func NewLegacyKeccak512() hash.Hash {
|
||||
return &state{rate: rateK1024, outputLen: 64, dsbyte: dsbyteKeccak}
|
||||
}
|
||||
|
||||
// spongeDirection indicates the direction bytes are flowing through the sponge.
|
||||
type spongeDirection int
|
||||
|
||||
@@ -173,12 +204,9 @@ func (d *state) Sum(in []byte) []byte {
|
||||
}
|
||||
|
||||
const (
|
||||
magicSHA3 = "sha\x08"
|
||||
magicShake = "sha\x09"
|
||||
magicCShake = "sha\x0a"
|
||||
magicKeccak = "sha\x0b"
|
||||
// magic || rate || main state || n || sponge direction
|
||||
marshaledSize = len(magicSHA3) + 1 + 200 + 1 + 1
|
||||
marshaledSize = len(magicKeccak) + 1 + 200 + 1 + 1
|
||||
)
|
||||
|
||||
func (d *state) MarshalBinary() ([]byte, error) {
|
||||
@@ -187,12 +215,6 @@ func (d *state) MarshalBinary() ([]byte, error) {
|
||||
|
||||
func (d *state) AppendBinary(b []byte) ([]byte, error) {
|
||||
switch d.dsbyte {
|
||||
case dsbyteSHA3:
|
||||
b = append(b, magicSHA3...)
|
||||
case dsbyteShake:
|
||||
b = append(b, magicShake...)
|
||||
case dsbyteCShake:
|
||||
b = append(b, magicCShake...)
|
||||
case dsbyteKeccak:
|
||||
b = append(b, magicKeccak...)
|
||||
default:
|
||||
@@ -210,12 +232,9 @@ func (d *state) UnmarshalBinary(b []byte) error {
|
||||
return errors.New("sha3: invalid hash state")
|
||||
}
|
||||
|
||||
magic := string(b[:len(magicSHA3)])
|
||||
b = b[len(magicSHA3):]
|
||||
magic := string(b[:len(magicKeccak)])
|
||||
b = b[len(magicKeccak):]
|
||||
switch {
|
||||
case magic == magicSHA3 && d.dsbyte == dsbyteSHA3:
|
||||
case magic == magicShake && d.dsbyte == dsbyteShake:
|
||||
case magic == magicCShake && d.dsbyte == dsbyteCShake:
|
||||
case magic == magicKeccak && d.dsbyte == dsbyteKeccak:
|
||||
default:
|
||||
return errors.New("sha3: invalid hash state identifier")
|
||||
Generated
Vendored
+4
-2
@@ -2,10 +2,12 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !amd64 || purego || !gc
|
||||
|
||||
package sha3
|
||||
|
||||
// This implementation is only used for NewLegacyKeccak256 and
|
||||
// NewLegacyKeccak512, which are not implemented by crypto/sha3.
|
||||
// All other functions in this package are wrappers around crypto/sha3.
|
||||
|
||||
import "math/bits"
|
||||
|
||||
// rc stores the round constants for use in the ι step.
|
||||
-303
@@ -1,303 +0,0 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build gc && !purego
|
||||
|
||||
package sha3
|
||||
|
||||
// This file contains code for using the 'compute intermediate
|
||||
// message digest' (KIMD) and 'compute last message digest' (KLMD)
|
||||
// instructions to compute SHA-3 and SHAKE hashes on IBM Z.
|
||||
|
||||
import (
|
||||
"hash"
|
||||
|
||||
"golang.org/x/sys/cpu"
|
||||
)
|
||||
|
||||
// codes represent 7-bit KIMD/KLMD function codes as defined in
|
||||
// the Principles of Operation.
|
||||
type code uint64
|
||||
|
||||
const (
|
||||
// function codes for KIMD/KLMD
|
||||
sha3_224 code = 32
|
||||
sha3_256 = 33
|
||||
sha3_384 = 34
|
||||
sha3_512 = 35
|
||||
shake_128 = 36
|
||||
shake_256 = 37
|
||||
nopad = 0x100
|
||||
)
|
||||
|
||||
// kimd is a wrapper for the 'compute intermediate message digest' instruction.
|
||||
// src must be a multiple of the rate for the given function code.
|
||||
//
|
||||
//go:noescape
|
||||
func kimd(function code, chain *[200]byte, src []byte)
|
||||
|
||||
// klmd is a wrapper for the 'compute last message digest' instruction.
|
||||
// src padding is handled by the instruction.
|
||||
//
|
||||
//go:noescape
|
||||
func klmd(function code, chain *[200]byte, dst, src []byte)
|
||||
|
||||
type asmState struct {
|
||||
a [200]byte // 1600 bit state
|
||||
buf []byte // care must be taken to ensure cap(buf) is a multiple of rate
|
||||
rate int // equivalent to block size
|
||||
storage [3072]byte // underlying storage for buf
|
||||
outputLen int // output length for full security
|
||||
function code // KIMD/KLMD function code
|
||||
state spongeDirection // whether the sponge is absorbing or squeezing
|
||||
}
|
||||
|
||||
func newAsmState(function code) *asmState {
|
||||
var s asmState
|
||||
s.function = function
|
||||
switch function {
|
||||
case sha3_224:
|
||||
s.rate = 144
|
||||
s.outputLen = 28
|
||||
case sha3_256:
|
||||
s.rate = 136
|
||||
s.outputLen = 32
|
||||
case sha3_384:
|
||||
s.rate = 104
|
||||
s.outputLen = 48
|
||||
case sha3_512:
|
||||
s.rate = 72
|
||||
s.outputLen = 64
|
||||
case shake_128:
|
||||
s.rate = 168
|
||||
s.outputLen = 32
|
||||
case shake_256:
|
||||
s.rate = 136
|
||||
s.outputLen = 64
|
||||
default:
|
||||
panic("sha3: unrecognized function code")
|
||||
}
|
||||
|
||||
// limit s.buf size to a multiple of s.rate
|
||||
s.resetBuf()
|
||||
return &s
|
||||
}
|
||||
|
||||
func (s *asmState) clone() *asmState {
|
||||
c := *s
|
||||
c.buf = c.storage[:len(s.buf):cap(s.buf)]
|
||||
return &c
|
||||
}
|
||||
|
||||
// copyIntoBuf copies b into buf. It will panic if there is not enough space to
|
||||
// store all of b.
|
||||
func (s *asmState) copyIntoBuf(b []byte) {
|
||||
bufLen := len(s.buf)
|
||||
s.buf = s.buf[:len(s.buf)+len(b)]
|
||||
copy(s.buf[bufLen:], b)
|
||||
}
|
||||
|
||||
// resetBuf points buf at storage, sets the length to 0 and sets cap to be a
|
||||
// multiple of the rate.
|
||||
func (s *asmState) resetBuf() {
|
||||
max := (cap(s.storage) / s.rate) * s.rate
|
||||
s.buf = s.storage[:0:max]
|
||||
}
|
||||
|
||||
// Write (via the embedded io.Writer interface) adds more data to the running hash.
|
||||
// It never returns an error.
|
||||
func (s *asmState) Write(b []byte) (int, error) {
|
||||
if s.state != spongeAbsorbing {
|
||||
panic("sha3: Write after Read")
|
||||
}
|
||||
length := len(b)
|
||||
for len(b) > 0 {
|
||||
if len(s.buf) == 0 && len(b) >= cap(s.buf) {
|
||||
// Hash the data directly and push any remaining bytes
|
||||
// into the buffer.
|
||||
remainder := len(b) % s.rate
|
||||
kimd(s.function, &s.a, b[:len(b)-remainder])
|
||||
if remainder != 0 {
|
||||
s.copyIntoBuf(b[len(b)-remainder:])
|
||||
}
|
||||
return length, nil
|
||||
}
|
||||
|
||||
if len(s.buf) == cap(s.buf) {
|
||||
// flush the buffer
|
||||
kimd(s.function, &s.a, s.buf)
|
||||
s.buf = s.buf[:0]
|
||||
}
|
||||
|
||||
// copy as much as we can into the buffer
|
||||
n := len(b)
|
||||
if len(b) > cap(s.buf)-len(s.buf) {
|
||||
n = cap(s.buf) - len(s.buf)
|
||||
}
|
||||
s.copyIntoBuf(b[:n])
|
||||
b = b[n:]
|
||||
}
|
||||
return length, nil
|
||||
}
|
||||
|
||||
// Read squeezes an arbitrary number of bytes from the sponge.
|
||||
func (s *asmState) Read(out []byte) (n int, err error) {
|
||||
// The 'compute last message digest' instruction only stores the digest
|
||||
// at the first operand (dst) for SHAKE functions.
|
||||
if s.function != shake_128 && s.function != shake_256 {
|
||||
panic("sha3: can only call Read for SHAKE functions")
|
||||
}
|
||||
|
||||
n = len(out)
|
||||
|
||||
// need to pad if we were absorbing
|
||||
if s.state == spongeAbsorbing {
|
||||
s.state = spongeSqueezing
|
||||
|
||||
// write hash directly into out if possible
|
||||
if len(out)%s.rate == 0 {
|
||||
klmd(s.function, &s.a, out, s.buf) // len(out) may be 0
|
||||
s.buf = s.buf[:0]
|
||||
return
|
||||
}
|
||||
|
||||
// write hash into buffer
|
||||
max := cap(s.buf)
|
||||
if max > len(out) {
|
||||
max = (len(out)/s.rate)*s.rate + s.rate
|
||||
}
|
||||
klmd(s.function, &s.a, s.buf[:max], s.buf)
|
||||
s.buf = s.buf[:max]
|
||||
}
|
||||
|
||||
for len(out) > 0 {
|
||||
// flush the buffer
|
||||
if len(s.buf) != 0 {
|
||||
c := copy(out, s.buf)
|
||||
out = out[c:]
|
||||
s.buf = s.buf[c:]
|
||||
continue
|
||||
}
|
||||
|
||||
// write hash directly into out if possible
|
||||
if len(out)%s.rate == 0 {
|
||||
klmd(s.function|nopad, &s.a, out, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// write hash into buffer
|
||||
s.resetBuf()
|
||||
if cap(s.buf) > len(out) {
|
||||
s.buf = s.buf[:(len(out)/s.rate)*s.rate+s.rate]
|
||||
}
|
||||
klmd(s.function|nopad, &s.a, s.buf, nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Sum appends the current hash to b and returns the resulting slice.
|
||||
// It does not change the underlying hash state.
|
||||
func (s *asmState) Sum(b []byte) []byte {
|
||||
if s.state != spongeAbsorbing {
|
||||
panic("sha3: Sum after Read")
|
||||
}
|
||||
|
||||
// Copy the state to preserve the original.
|
||||
a := s.a
|
||||
|
||||
// Hash the buffer. Note that we don't clear it because we
|
||||
// aren't updating the state.
|
||||
switch s.function {
|
||||
case sha3_224, sha3_256, sha3_384, sha3_512:
|
||||
klmd(s.function, &a, nil, s.buf)
|
||||
return append(b, a[:s.outputLen]...)
|
||||
case shake_128, shake_256:
|
||||
d := make([]byte, s.outputLen, 64)
|
||||
klmd(s.function, &a, d, s.buf)
|
||||
return append(b, d[:s.outputLen]...)
|
||||
default:
|
||||
panic("sha3: unknown function")
|
||||
}
|
||||
}
|
||||
|
||||
// Reset resets the Hash to its initial state.
|
||||
func (s *asmState) Reset() {
|
||||
for i := range s.a {
|
||||
s.a[i] = 0
|
||||
}
|
||||
s.resetBuf()
|
||||
s.state = spongeAbsorbing
|
||||
}
|
||||
|
||||
// Size returns the number of bytes Sum will return.
|
||||
func (s *asmState) Size() int {
|
||||
return s.outputLen
|
||||
}
|
||||
|
||||
// BlockSize returns the hash's underlying block size.
|
||||
// The Write method must be able to accept any amount
|
||||
// of data, but it may operate more efficiently if all writes
|
||||
// are a multiple of the block size.
|
||||
func (s *asmState) BlockSize() int {
|
||||
return s.rate
|
||||
}
|
||||
|
||||
// Clone returns a copy of the ShakeHash in its current state.
|
||||
func (s *asmState) Clone() ShakeHash {
|
||||
return s.clone()
|
||||
}
|
||||
|
||||
// new224 returns an assembly implementation of SHA3-224 if available,
|
||||
// otherwise it returns a generic implementation.
|
||||
func new224() hash.Hash {
|
||||
if cpu.S390X.HasSHA3 {
|
||||
return newAsmState(sha3_224)
|
||||
}
|
||||
return new224Generic()
|
||||
}
|
||||
|
||||
// new256 returns an assembly implementation of SHA3-256 if available,
|
||||
// otherwise it returns a generic implementation.
|
||||
func new256() hash.Hash {
|
||||
if cpu.S390X.HasSHA3 {
|
||||
return newAsmState(sha3_256)
|
||||
}
|
||||
return new256Generic()
|
||||
}
|
||||
|
||||
// new384 returns an assembly implementation of SHA3-384 if available,
|
||||
// otherwise it returns a generic implementation.
|
||||
func new384() hash.Hash {
|
||||
if cpu.S390X.HasSHA3 {
|
||||
return newAsmState(sha3_384)
|
||||
}
|
||||
return new384Generic()
|
||||
}
|
||||
|
||||
// new512 returns an assembly implementation of SHA3-512 if available,
|
||||
// otherwise it returns a generic implementation.
|
||||
func new512() hash.Hash {
|
||||
if cpu.S390X.HasSHA3 {
|
||||
return newAsmState(sha3_512)
|
||||
}
|
||||
return new512Generic()
|
||||
}
|
||||
|
||||
// newShake128 returns an assembly implementation of SHAKE-128 if available,
|
||||
// otherwise it returns a generic implementation.
|
||||
func newShake128() ShakeHash {
|
||||
if cpu.S390X.HasSHA3 {
|
||||
return newAsmState(shake_128)
|
||||
}
|
||||
return newShake128Generic()
|
||||
}
|
||||
|
||||
// newShake256 returns an assembly implementation of SHAKE-256 if available,
|
||||
// otherwise it returns a generic implementation.
|
||||
func newShake256() ShakeHash {
|
||||
if cpu.S390X.HasSHA3 {
|
||||
return newAsmState(shake_256)
|
||||
}
|
||||
return newShake256Generic()
|
||||
}
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build gc && !purego
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func kimd(function code, chain *[200]byte, src []byte)
|
||||
TEXT ·kimd(SB), NOFRAME|NOSPLIT, $0-40
|
||||
MOVD function+0(FP), R0
|
||||
MOVD chain+8(FP), R1
|
||||
LMG src+16(FP), R2, R3 // R2=base, R3=len
|
||||
|
||||
continue:
|
||||
WORD $0xB93E0002 // KIMD --, R2
|
||||
BVS continue // continue if interrupted
|
||||
MOVD $0, R0 // reset R0 for pre-go1.8 compilers
|
||||
RET
|
||||
|
||||
// func klmd(function code, chain *[200]byte, dst, src []byte)
|
||||
TEXT ·klmd(SB), NOFRAME|NOSPLIT, $0-64
|
||||
// TODO: SHAKE support
|
||||
MOVD function+0(FP), R0
|
||||
MOVD chain+8(FP), R1
|
||||
LMG dst+16(FP), R2, R3 // R2=base, R3=len
|
||||
LMG src+40(FP), R4, R5 // R4=base, R5=len
|
||||
|
||||
continue:
|
||||
WORD $0xB93F0024 // KLMD R2, R4
|
||||
BVS continue // continue if interrupted
|
||||
MOVD $0, R0 // reset R0 for pre-go1.8 compilers
|
||||
RET
|
||||
+49
-123
@@ -4,24 +4,10 @@
|
||||
|
||||
package sha3
|
||||
|
||||
// This file defines the ShakeHash interface, and provides
|
||||
// functions for creating SHAKE and cSHAKE instances, as well as utility
|
||||
// functions for hashing bytes to arbitrary-length output.
|
||||
//
|
||||
//
|
||||
// SHAKE implementation is based on FIPS PUB 202 [1]
|
||||
// cSHAKE implementations is based on NIST SP 800-185 [2]
|
||||
//
|
||||
// [1] https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf
|
||||
// [2] https://doi.org/10.6028/NIST.SP.800-185
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"crypto/sha3"
|
||||
"hash"
|
||||
"io"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
// ShakeHash defines the interface to hash functions that support
|
||||
@@ -32,7 +18,7 @@ type ShakeHash interface {
|
||||
hash.Hash
|
||||
|
||||
// Read reads more output from the hash; reading affects the hash's
|
||||
// state. (ShakeHash.Read is thus very different from Hash.Sum)
|
||||
// state. (ShakeHash.Read is thus very different from Hash.Sum.)
|
||||
// It never returns an error, but subsequent calls to Write or Sum
|
||||
// will panic.
|
||||
io.Reader
|
||||
@@ -41,115 +27,18 @@ type ShakeHash interface {
|
||||
Clone() ShakeHash
|
||||
}
|
||||
|
||||
// cSHAKE specific context
|
||||
type cshakeState struct {
|
||||
*state // SHA-3 state context and Read/Write operations
|
||||
|
||||
// initBlock is the cSHAKE specific initialization set of bytes. It is initialized
|
||||
// by newCShake function and stores concatenation of N followed by S, encoded
|
||||
// by the method specified in 3.3 of [1].
|
||||
// It is stored here in order for Reset() to be able to put context into
|
||||
// initial state.
|
||||
initBlock []byte
|
||||
}
|
||||
|
||||
func bytepad(data []byte, rate int) []byte {
|
||||
out := make([]byte, 0, 9+len(data)+rate-1)
|
||||
out = append(out, leftEncode(uint64(rate))...)
|
||||
out = append(out, data...)
|
||||
if padlen := rate - len(out)%rate; padlen < rate {
|
||||
out = append(out, make([]byte, padlen)...)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func leftEncode(x uint64) []byte {
|
||||
// Let n be the smallest positive integer for which 2^(8n) > x.
|
||||
n := (bits.Len64(x) + 7) / 8
|
||||
if n == 0 {
|
||||
n = 1
|
||||
}
|
||||
// Return n || x with n as a byte and x an n bytes in big-endian order.
|
||||
b := make([]byte, 9)
|
||||
binary.BigEndian.PutUint64(b[1:], x)
|
||||
b = b[9-n-1:]
|
||||
b[0] = byte(n)
|
||||
return b
|
||||
}
|
||||
|
||||
func newCShake(N, S []byte, rate, outputLen int, dsbyte byte) ShakeHash {
|
||||
c := cshakeState{state: &state{rate: rate, outputLen: outputLen, dsbyte: dsbyte}}
|
||||
c.initBlock = make([]byte, 0, 9+len(N)+9+len(S)) // leftEncode returns max 9 bytes
|
||||
c.initBlock = append(c.initBlock, leftEncode(uint64(len(N))*8)...)
|
||||
c.initBlock = append(c.initBlock, N...)
|
||||
c.initBlock = append(c.initBlock, leftEncode(uint64(len(S))*8)...)
|
||||
c.initBlock = append(c.initBlock, S...)
|
||||
c.Write(bytepad(c.initBlock, c.rate))
|
||||
return &c
|
||||
}
|
||||
|
||||
// Reset resets the hash to initial state.
|
||||
func (c *cshakeState) Reset() {
|
||||
c.state.Reset()
|
||||
c.Write(bytepad(c.initBlock, c.rate))
|
||||
}
|
||||
|
||||
// Clone returns copy of a cSHAKE context within its current state.
|
||||
func (c *cshakeState) Clone() ShakeHash {
|
||||
b := make([]byte, len(c.initBlock))
|
||||
copy(b, c.initBlock)
|
||||
return &cshakeState{state: c.clone(), initBlock: b}
|
||||
}
|
||||
|
||||
// Clone returns copy of SHAKE context within its current state.
|
||||
func (c *state) Clone() ShakeHash {
|
||||
return c.clone()
|
||||
}
|
||||
|
||||
func (c *cshakeState) MarshalBinary() ([]byte, error) {
|
||||
return c.AppendBinary(make([]byte, 0, marshaledSize+len(c.initBlock)))
|
||||
}
|
||||
|
||||
func (c *cshakeState) AppendBinary(b []byte) ([]byte, error) {
|
||||
b, err := c.state.AppendBinary(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b = append(b, c.initBlock...)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (c *cshakeState) UnmarshalBinary(b []byte) error {
|
||||
if len(b) <= marshaledSize {
|
||||
return errors.New("sha3: invalid hash state")
|
||||
}
|
||||
if err := c.state.UnmarshalBinary(b[:marshaledSize]); err != nil {
|
||||
return err
|
||||
}
|
||||
c.initBlock = bytes.Clone(b[marshaledSize:])
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewShake128 creates a new SHAKE128 variable-output-length ShakeHash.
|
||||
// Its generic security strength is 128 bits against all attacks if at
|
||||
// least 32 bytes of its output are used.
|
||||
func NewShake128() ShakeHash {
|
||||
return newShake128()
|
||||
return &shakeWrapper{sha3.NewSHAKE128(), 32, false, sha3.NewSHAKE128}
|
||||
}
|
||||
|
||||
// NewShake256 creates a new SHAKE256 variable-output-length ShakeHash.
|
||||
// Its generic security strength is 256 bits against all attacks if
|
||||
// at least 64 bytes of its output are used.
|
||||
func NewShake256() ShakeHash {
|
||||
return newShake256()
|
||||
}
|
||||
|
||||
func newShake128Generic() *state {
|
||||
return &state{rate: rateK256, outputLen: 32, dsbyte: dsbyteShake}
|
||||
}
|
||||
|
||||
func newShake256Generic() *state {
|
||||
return &state{rate: rateK512, outputLen: 64, dsbyte: dsbyteShake}
|
||||
return &shakeWrapper{sha3.NewSHAKE256(), 64, false, sha3.NewSHAKE256}
|
||||
}
|
||||
|
||||
// NewCShake128 creates a new instance of cSHAKE128 variable-output-length ShakeHash,
|
||||
@@ -159,10 +48,9 @@ func newShake256Generic() *state {
|
||||
// computations on same input with different S yield unrelated outputs.
|
||||
// When N and S are both empty, this is equivalent to NewShake128.
|
||||
func NewCShake128(N, S []byte) ShakeHash {
|
||||
if len(N) == 0 && len(S) == 0 {
|
||||
return NewShake128()
|
||||
}
|
||||
return newCShake(N, S, rateK256, 32, dsbyteCShake)
|
||||
return &shakeWrapper{sha3.NewCSHAKE128(N, S), 32, false, func() *sha3.SHAKE {
|
||||
return sha3.NewCSHAKE128(N, S)
|
||||
}}
|
||||
}
|
||||
|
||||
// NewCShake256 creates a new instance of cSHAKE256 variable-output-length ShakeHash,
|
||||
@@ -172,10 +60,9 @@ func NewCShake128(N, S []byte) ShakeHash {
|
||||
// computations on same input with different S yield unrelated outputs.
|
||||
// When N and S are both empty, this is equivalent to NewShake256.
|
||||
func NewCShake256(N, S []byte) ShakeHash {
|
||||
if len(N) == 0 && len(S) == 0 {
|
||||
return NewShake256()
|
||||
}
|
||||
return newCShake(N, S, rateK512, 64, dsbyteCShake)
|
||||
return &shakeWrapper{sha3.NewCSHAKE256(N, S), 64, false, func() *sha3.SHAKE {
|
||||
return sha3.NewCSHAKE256(N, S)
|
||||
}}
|
||||
}
|
||||
|
||||
// ShakeSum128 writes an arbitrary-length digest of data into hash.
|
||||
@@ -191,3 +78,42 @@ func ShakeSum256(hash, data []byte) {
|
||||
h.Write(data)
|
||||
h.Read(hash)
|
||||
}
|
||||
|
||||
// shakeWrapper adds the Size, Sum, and Clone methods to a sha3.SHAKE
|
||||
// to implement the ShakeHash interface.
|
||||
type shakeWrapper struct {
|
||||
*sha3.SHAKE
|
||||
outputLen int
|
||||
squeezing bool
|
||||
newSHAKE func() *sha3.SHAKE
|
||||
}
|
||||
|
||||
func (w *shakeWrapper) Read(p []byte) (n int, err error) {
|
||||
w.squeezing = true
|
||||
return w.SHAKE.Read(p)
|
||||
}
|
||||
|
||||
func (w *shakeWrapper) Clone() ShakeHash {
|
||||
s := w.newSHAKE()
|
||||
b, err := w.MarshalBinary()
|
||||
if err != nil {
|
||||
panic(err) // unreachable
|
||||
}
|
||||
if err := s.UnmarshalBinary(b); err != nil {
|
||||
panic(err) // unreachable
|
||||
}
|
||||
return &shakeWrapper{s, w.outputLen, w.squeezing, w.newSHAKE}
|
||||
}
|
||||
|
||||
func (w *shakeWrapper) Size() int { return w.outputLen }
|
||||
|
||||
func (w *shakeWrapper) Sum(b []byte) []byte {
|
||||
if w.squeezing {
|
||||
panic("sha3: Sum after Read")
|
||||
}
|
||||
out := make([]byte, w.outputLen)
|
||||
// Clone the state so that we don't affect future Write calls.
|
||||
s := w.Clone()
|
||||
s.Read(out)
|
||||
return append(b, out...)
|
||||
}
|
||||
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !gc || purego || !s390x
|
||||
|
||||
package sha3
|
||||
|
||||
func newShake128() *state {
|
||||
return newShake128Generic()
|
||||
}
|
||||
|
||||
func newShake256() *state {
|
||||
return newShake256Generic()
|
||||
}
|
||||
+1
-1
@@ -33,7 +33,7 @@ type printer struct {
|
||||
}
|
||||
|
||||
// printf prints to the buffer.
|
||||
func (p *printer) printf(format string, args ...interface{}) {
|
||||
func (p *printer) printf(format string, args ...any) {
|
||||
fmt.Fprintf(p, format, args...)
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -94,7 +94,7 @@ func (x *FileSyntax) Span() (start, end Position) {
|
||||
// line, the new line is added at the end of the block containing hint,
|
||||
// extracting hint into a new block if it is not yet in one.
|
||||
//
|
||||
// If the hint is non-nil buts its first token does not match,
|
||||
// If the hint is non-nil but its first token does not match,
|
||||
// the new line is added after the block containing hint
|
||||
// (or hint itself, if not in a block).
|
||||
//
|
||||
@@ -600,7 +600,7 @@ func (in *input) readToken() {
|
||||
|
||||
// Checked all punctuation. Must be identifier token.
|
||||
if c := in.peekRune(); !isIdent(c) {
|
||||
in.Error(fmt.Sprintf("unexpected input character %#q", c))
|
||||
in.Error(fmt.Sprintf("unexpected input character %#q", rune(c)))
|
||||
}
|
||||
|
||||
// Scan over identifier.
|
||||
|
||||
+4
-4
@@ -368,7 +368,7 @@ func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, a
|
||||
Err: err,
|
||||
})
|
||||
}
|
||||
errorf := func(format string, args ...interface{}) {
|
||||
errorf := func(format string, args ...any) {
|
||||
wrapError(fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
@@ -574,7 +574,7 @@ func parseReplace(filename string, line *Line, verb string, args []string, fix V
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
errorf := func(format string, args ...interface{}) *Error {
|
||||
errorf := func(format string, args ...any) *Error {
|
||||
return wrapError(fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
@@ -685,7 +685,7 @@ func (f *WorkFile) add(errs *ErrorList, line *Line, verb string, args []string,
|
||||
Err: err,
|
||||
})
|
||||
}
|
||||
errorf := func(format string, args ...interface{}) {
|
||||
errorf := func(format string, args ...any) {
|
||||
wrapError(fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
@@ -1594,7 +1594,7 @@ func (f *File) AddRetract(vi VersionInterval, rationale string) error {
|
||||
r.Syntax = f.Syntax.addLine(nil, "retract", "[", AutoQuote(vi.Low), ",", AutoQuote(vi.High), "]")
|
||||
}
|
||||
if rationale != "" {
|
||||
for _, line := range strings.Split(rationale, "\n") {
|
||||
for line := range strings.SplitSeq(rationale, "\n") {
|
||||
com := Comment{Token: "// " + line}
|
||||
r.Syntax.Comment().Before = append(r.Syntax.Comment().Before, com)
|
||||
}
|
||||
|
||||
+3
-3
@@ -261,7 +261,7 @@ func modPathOK(r rune) bool {
|
||||
|
||||
// importPathOK reports whether r can appear in a package import path element.
|
||||
//
|
||||
// Import paths are intermediate between module paths and file paths: we allow
|
||||
// Import paths are intermediate between module paths and file paths: we
|
||||
// disallow characters that would be confusing or ambiguous as arguments to
|
||||
// 'go get' (such as '@' and ' ' ), but allow certain characters that are
|
||||
// otherwise-unambiguous on the command line and historically used for some
|
||||
@@ -802,8 +802,8 @@ func MatchPrefixPatterns(globs, target string) bool {
|
||||
for globs != "" {
|
||||
// Extract next non-empty glob in comma-separated list.
|
||||
var glob string
|
||||
if i := strings.Index(globs, ","); i >= 0 {
|
||||
glob, globs = globs[:i], globs[i+1:]
|
||||
if before, after, ok := strings.Cut(globs, ","); ok {
|
||||
glob, globs = before, after
|
||||
} else {
|
||||
glob, globs = globs, ""
|
||||
}
|
||||
|
||||
+2
-2
@@ -45,8 +45,8 @@ func IsValid(v string) bool {
|
||||
|
||||
// Canonical returns the canonical formatting of the semantic version v.
|
||||
// It fills in any missing .MINOR or .PATCH and discards build metadata.
|
||||
// Two semantic versions compare equal only if their canonical formattings
|
||||
// are identical strings.
|
||||
// Two semantic versions compare equal only if their canonical formatting
|
||||
// is an identical string.
|
||||
// The canonical invalid semantic version is the empty string.
|
||||
func Canonical(v string) string {
|
||||
p, ok := parse(v)
|
||||
|
||||
+2
-35
@@ -2,42 +2,9 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package context defines the Context type, which carries deadlines,
|
||||
// cancellation signals, and other request-scoped values across API boundaries
|
||||
// and between processes.
|
||||
// As of Go 1.7 this package is available in the standard library under the
|
||||
// name [context].
|
||||
// Package context has been superseded by the standard library [context] package.
|
||||
//
|
||||
// Incoming requests to a server should create a [Context], and outgoing
|
||||
// calls to servers should accept a Context. The chain of function
|
||||
// calls between them must propagate the Context, optionally replacing
|
||||
// it with a derived Context created using [WithCancel], [WithDeadline],
|
||||
// [WithTimeout], or [WithValue].
|
||||
//
|
||||
// Programs that use Contexts should follow these rules to keep interfaces
|
||||
// consistent across packages and enable static analysis tools to check context
|
||||
// propagation:
|
||||
//
|
||||
// Do not store Contexts inside a struct type; instead, pass a Context
|
||||
// explicitly to each function that needs it. This is discussed further in
|
||||
// https://go.dev/blog/context-and-structs. The Context should be the first
|
||||
// parameter, typically named ctx:
|
||||
//
|
||||
// func DoSomething(ctx context.Context, arg Arg) error {
|
||||
// // ... use ctx ...
|
||||
// }
|
||||
//
|
||||
// Do not pass a nil [Context], even if a function permits it. Pass [context.TODO]
|
||||
// if you are unsure about which Context to use.
|
||||
//
|
||||
// Use context Values only for request-scoped data that transits processes and
|
||||
// APIs, not for passing optional parameters to functions.
|
||||
//
|
||||
// The same Context may be passed to functions running in different goroutines;
|
||||
// Contexts are safe for simultaneous use by multiple goroutines.
|
||||
//
|
||||
// See https://go.dev/blog/context for example code for a server that uses
|
||||
// Contexts.
|
||||
// Deprecated: Use the standard library context package instead.
|
||||
package context
|
||||
|
||||
import (
|
||||
|
||||
-2
@@ -2,8 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.23
|
||||
|
||||
package html
|
||||
|
||||
import "iter"
|
||||
|
||||
+1
@@ -11,6 +11,7 @@ import (
|
||||
// A NodeType is the type of a Node.
|
||||
type NodeType uint32
|
||||
|
||||
//go:generate stringer -type NodeType
|
||||
const (
|
||||
ErrorNode NodeType = iota
|
||||
TextNode
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
// Code generated by "stringer -type NodeType"; DO NOT EDIT.
|
||||
|
||||
package html
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[ErrorNode-0]
|
||||
_ = x[TextNode-1]
|
||||
_ = x[DocumentNode-2]
|
||||
_ = x[ElementNode-3]
|
||||
_ = x[CommentNode-4]
|
||||
_ = x[DoctypeNode-5]
|
||||
_ = x[RawNode-6]
|
||||
_ = x[scopeMarkerNode-7]
|
||||
}
|
||||
|
||||
const _NodeType_name = "ErrorNodeTextNodeDocumentNodeElementNodeCommentNodeDoctypeNodeRawNodescopeMarkerNode"
|
||||
|
||||
var _NodeType_index = [...]uint8{0, 9, 17, 29, 40, 51, 62, 69, 84}
|
||||
|
||||
func (i NodeType) String() string {
|
||||
idx := int(i) - 0
|
||||
if i < 0 || idx >= len(_NodeType_index)-1 {
|
||||
return "NodeType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _NodeType_name[_NodeType_index[idx]:_NodeType_index[idx+1]]
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !go1.27
|
||||
|
||||
package http2
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Support for go.dev/issue/75500 is added in Go 1.27. In case anyone uses
|
||||
// x/net with versions before Go 1.27, we return true here so that their write
|
||||
// scheduler will still be the round-robin write scheduler rather than the RFC
|
||||
// 9218 write scheduler. That way, older users of Go will not see a sudden
|
||||
// change of behavior just from importing x/net.
|
||||
//
|
||||
// TODO(nsh): remove this file after x/net go.mod is at Go 1.27.
|
||||
func clientPriorityDisabled(_ *http.Server) bool {
|
||||
return true
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.27
|
||||
|
||||
package http2
|
||||
|
||||
import "net/http"
|
||||
|
||||
func clientPriorityDisabled(s *http.Server) bool {
|
||||
return s.DisableClientPriority
|
||||
}
|
||||
+205
-59
@@ -11,11 +11,13 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/http/httpguts"
|
||||
"golang.org/x/net/http2/hpack"
|
||||
"golang.org/x/net/internal/httpsfv"
|
||||
)
|
||||
|
||||
const frameHeaderLen = 9
|
||||
@@ -23,33 +25,36 @@ const frameHeaderLen = 9
|
||||
var padZeros = make([]byte, 255) // zeros for padding
|
||||
|
||||
// A FrameType is a registered frame type as defined in
|
||||
// https://httpwg.org/specs/rfc7540.html#rfc.section.11.2
|
||||
// https://httpwg.org/specs/rfc7540.html#rfc.section.11.2 and other future
|
||||
// RFCs.
|
||||
type FrameType uint8
|
||||
|
||||
const (
|
||||
FrameData FrameType = 0x0
|
||||
FrameHeaders FrameType = 0x1
|
||||
FramePriority FrameType = 0x2
|
||||
FrameRSTStream FrameType = 0x3
|
||||
FrameSettings FrameType = 0x4
|
||||
FramePushPromise FrameType = 0x5
|
||||
FramePing FrameType = 0x6
|
||||
FrameGoAway FrameType = 0x7
|
||||
FrameWindowUpdate FrameType = 0x8
|
||||
FrameContinuation FrameType = 0x9
|
||||
FrameData FrameType = 0x0
|
||||
FrameHeaders FrameType = 0x1
|
||||
FramePriority FrameType = 0x2
|
||||
FrameRSTStream FrameType = 0x3
|
||||
FrameSettings FrameType = 0x4
|
||||
FramePushPromise FrameType = 0x5
|
||||
FramePing FrameType = 0x6
|
||||
FrameGoAway FrameType = 0x7
|
||||
FrameWindowUpdate FrameType = 0x8
|
||||
FrameContinuation FrameType = 0x9
|
||||
FramePriorityUpdate FrameType = 0x10
|
||||
)
|
||||
|
||||
var frameNames = [...]string{
|
||||
FrameData: "DATA",
|
||||
FrameHeaders: "HEADERS",
|
||||
FramePriority: "PRIORITY",
|
||||
FrameRSTStream: "RST_STREAM",
|
||||
FrameSettings: "SETTINGS",
|
||||
FramePushPromise: "PUSH_PROMISE",
|
||||
FramePing: "PING",
|
||||
FrameGoAway: "GOAWAY",
|
||||
FrameWindowUpdate: "WINDOW_UPDATE",
|
||||
FrameContinuation: "CONTINUATION",
|
||||
FrameData: "DATA",
|
||||
FrameHeaders: "HEADERS",
|
||||
FramePriority: "PRIORITY",
|
||||
FrameRSTStream: "RST_STREAM",
|
||||
FrameSettings: "SETTINGS",
|
||||
FramePushPromise: "PUSH_PROMISE",
|
||||
FramePing: "PING",
|
||||
FrameGoAway: "GOAWAY",
|
||||
FrameWindowUpdate: "WINDOW_UPDATE",
|
||||
FrameContinuation: "CONTINUATION",
|
||||
FramePriorityUpdate: "PRIORITY_UPDATE",
|
||||
}
|
||||
|
||||
func (t FrameType) String() string {
|
||||
@@ -125,21 +130,24 @@ var flagName = map[FrameType]map[Flags]string{
|
||||
type frameParser func(fc *frameCache, fh FrameHeader, countError func(string), payload []byte) (Frame, error)
|
||||
|
||||
var frameParsers = [...]frameParser{
|
||||
FrameData: parseDataFrame,
|
||||
FrameHeaders: parseHeadersFrame,
|
||||
FramePriority: parsePriorityFrame,
|
||||
FrameRSTStream: parseRSTStreamFrame,
|
||||
FrameSettings: parseSettingsFrame,
|
||||
FramePushPromise: parsePushPromise,
|
||||
FramePing: parsePingFrame,
|
||||
FrameGoAway: parseGoAwayFrame,
|
||||
FrameWindowUpdate: parseWindowUpdateFrame,
|
||||
FrameContinuation: parseContinuationFrame,
|
||||
FrameData: parseDataFrame,
|
||||
FrameHeaders: parseHeadersFrame,
|
||||
FramePriority: parsePriorityFrame,
|
||||
FrameRSTStream: parseRSTStreamFrame,
|
||||
FrameSettings: parseSettingsFrame,
|
||||
FramePushPromise: parsePushPromise,
|
||||
FramePing: parsePingFrame,
|
||||
FrameGoAway: parseGoAwayFrame,
|
||||
FrameWindowUpdate: parseWindowUpdateFrame,
|
||||
FrameContinuation: parseContinuationFrame,
|
||||
FramePriorityUpdate: parsePriorityUpdateFrame,
|
||||
}
|
||||
|
||||
func typeFrameParser(t FrameType) frameParser {
|
||||
if int(t) < len(frameParsers) {
|
||||
return frameParsers[t]
|
||||
if f := frameParsers[t]; f != nil {
|
||||
return f
|
||||
}
|
||||
}
|
||||
return parseUnknownFrame
|
||||
}
|
||||
@@ -280,6 +288,8 @@ type Framer struct {
|
||||
// lastHeaderStream is non-zero if the last frame was an
|
||||
// unfinished HEADERS/CONTINUATION.
|
||||
lastHeaderStream uint32
|
||||
// lastFrameType holds the type of the last frame for verifying frame order.
|
||||
lastFrameType FrameType
|
||||
|
||||
maxReadSize uint32
|
||||
headerBuf [frameHeaderLen]byte
|
||||
@@ -488,30 +498,41 @@ func terminalReadFrameError(err error) bool {
|
||||
return err != nil
|
||||
}
|
||||
|
||||
// ReadFrame reads a single frame. The returned Frame is only valid
|
||||
// until the next call to ReadFrame.
|
||||
// ReadFrameHeader reads the header of the next frame.
|
||||
// It reads the 9-byte fixed frame header, and does not read any portion of the
|
||||
// frame payload. The caller is responsible for consuming the payload, either
|
||||
// with ReadFrameForHeader or directly from the Framer's io.Reader.
|
||||
//
|
||||
// If the frame is larger than previously set with SetMaxReadFrameSize, the
|
||||
// returned error is ErrFrameTooLarge. Other errors may be of type
|
||||
// ConnectionError, StreamError, or anything else from the underlying
|
||||
// reader.
|
||||
// If the frame is larger than previously set with SetMaxReadFrameSize, it
|
||||
// returns the frame header and ErrFrameTooLarge.
|
||||
//
|
||||
// If ReadFrame returns an error and a non-nil Frame, the Frame's StreamID
|
||||
// indicates the stream responsible for the error.
|
||||
func (fr *Framer) ReadFrame() (Frame, error) {
|
||||
// If the returned FrameHeader.StreamID is non-zero, it indicates the stream
|
||||
// responsible for the error.
|
||||
func (fr *Framer) ReadFrameHeader() (FrameHeader, error) {
|
||||
fr.errDetail = nil
|
||||
if fr.lastFrame != nil {
|
||||
fr.lastFrame.invalidate()
|
||||
}
|
||||
fh, err := readFrameHeader(fr.headerBuf[:], fr.r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return fh, err
|
||||
}
|
||||
if fh.Length > fr.maxReadSize {
|
||||
if fh == invalidHTTP1LookingFrameHeader() {
|
||||
return nil, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", ErrFrameTooLarge)
|
||||
return fh, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", ErrFrameTooLarge)
|
||||
}
|
||||
return nil, ErrFrameTooLarge
|
||||
return fh, ErrFrameTooLarge
|
||||
}
|
||||
if err := fr.checkFrameOrder(fh); err != nil {
|
||||
return fh, err
|
||||
}
|
||||
return fh, nil
|
||||
}
|
||||
|
||||
// ReadFrameForHeader reads the payload for the frame with the given FrameHeader.
|
||||
//
|
||||
// It behaves identically to ReadFrame, other than not checking the maximum
|
||||
// frame size.
|
||||
func (fr *Framer) ReadFrameForHeader(fh FrameHeader) (Frame, error) {
|
||||
if fr.lastFrame != nil {
|
||||
fr.lastFrame.invalidate()
|
||||
}
|
||||
payload := fr.getReadBuf(fh.Length)
|
||||
if _, err := io.ReadFull(fr.r, payload); err != nil {
|
||||
@@ -527,9 +548,7 @@ func (fr *Framer) ReadFrame() (Frame, error) {
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if err := fr.checkFrameOrder(f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fr.lastFrame = f
|
||||
if fr.logReads {
|
||||
fr.debugReadLoggerf("http2: Framer %p: read %v", fr, summarizeFrame(f))
|
||||
}
|
||||
@@ -539,6 +558,24 @@ func (fr *Framer) ReadFrame() (Frame, error) {
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ReadFrame reads a single frame. The returned Frame is only valid
|
||||
// until the next call to ReadFrame or ReadFrameBodyForHeader.
|
||||
//
|
||||
// If the frame is larger than previously set with SetMaxReadFrameSize, the
|
||||
// returned error is ErrFrameTooLarge. Other errors may be of type
|
||||
// ConnectionError, StreamError, or anything else from the underlying
|
||||
// reader.
|
||||
//
|
||||
// If ReadFrame returns an error and a non-nil Frame, the Frame's StreamID
|
||||
// indicates the stream responsible for the error.
|
||||
func (fr *Framer) ReadFrame() (Frame, error) {
|
||||
fh, err := fr.ReadFrameHeader()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fr.ReadFrameForHeader(fh)
|
||||
}
|
||||
|
||||
// connError returns ConnectionError(code) but first
|
||||
// stashes away a public reason to the caller can optionally relay it
|
||||
// to the peer before hanging up on them. This might help others debug
|
||||
@@ -551,20 +588,19 @@ func (fr *Framer) connError(code ErrCode, reason string) error {
|
||||
// checkFrameOrder reports an error if f is an invalid frame to return
|
||||
// next from ReadFrame. Mostly it checks whether HEADERS and
|
||||
// CONTINUATION frames are contiguous.
|
||||
func (fr *Framer) checkFrameOrder(f Frame) error {
|
||||
last := fr.lastFrame
|
||||
fr.lastFrame = f
|
||||
func (fr *Framer) checkFrameOrder(fh FrameHeader) error {
|
||||
lastType := fr.lastFrameType
|
||||
fr.lastFrameType = fh.Type
|
||||
if fr.AllowIllegalReads {
|
||||
return nil
|
||||
}
|
||||
|
||||
fh := f.Header()
|
||||
if fr.lastHeaderStream != 0 {
|
||||
if fh.Type != FrameContinuation {
|
||||
return fr.connError(ErrCodeProtocol,
|
||||
fmt.Sprintf("got %s for stream %d; expected CONTINUATION following %s for stream %d",
|
||||
fh.Type, fh.StreamID,
|
||||
last.Header().Type, fr.lastHeaderStream))
|
||||
lastType, fr.lastHeaderStream))
|
||||
}
|
||||
if fh.StreamID != fr.lastHeaderStream {
|
||||
return fr.connError(ErrCodeProtocol,
|
||||
@@ -1152,16 +1188,41 @@ type PriorityFrame struct {
|
||||
PriorityParam
|
||||
}
|
||||
|
||||
var defaultRFC9218Priority = PriorityParam{
|
||||
incremental: 0,
|
||||
urgency: 3,
|
||||
// defaultRFC9218Priority determines what priority we should use as the default
|
||||
// value.
|
||||
//
|
||||
// According to RFC 9218, by default, streams should be given an urgency of 3
|
||||
// and should be non-incremental. However, making streams non-incremental by
|
||||
// default would be a huge change to our historical behavior where we would
|
||||
// round-robin writes across streams. When streams are non-incremental, we
|
||||
// would process streams of the same urgency one-by-one to completion instead.
|
||||
//
|
||||
// To avoid such a sudden change which might break some HTTP/2 users, this
|
||||
// function allows the caller to specify whether they can actually use the
|
||||
// default value as specified in RFC 9218. If not, this function will return a
|
||||
// priority value where streams are incremental by default instead: effectively
|
||||
// a round-robin between stream of the same urgency.
|
||||
//
|
||||
// As an example, a server might not be able to use the RFC 9218 default value
|
||||
// when it's not sure that the client it is serving is aware of RFC 9218.
|
||||
func defaultRFC9218Priority(canUseDefault bool) PriorityParam {
|
||||
if canUseDefault {
|
||||
return PriorityParam{
|
||||
urgency: 3,
|
||||
incremental: 0,
|
||||
}
|
||||
}
|
||||
return PriorityParam{
|
||||
urgency: 3,
|
||||
incremental: 1,
|
||||
}
|
||||
}
|
||||
|
||||
// Note that HTTP/2 has had two different prioritization schemes, and
|
||||
// PriorityParam struct below is a superset of both schemes. The exported
|
||||
// symbols are from RFC 7540 and the non-exported ones are from RFC 9218.
|
||||
|
||||
// PriorityParam are the stream prioritzation parameters.
|
||||
// PriorityParam are the stream prioritization parameters.
|
||||
type PriorityParam struct {
|
||||
// StreamDep is a 31-bit stream identifier for the
|
||||
// stream that this stream depends on. Zero means no
|
||||
@@ -1238,6 +1299,74 @@ func (f *Framer) WritePriority(streamID uint32, p PriorityParam) error {
|
||||
return f.endWrite()
|
||||
}
|
||||
|
||||
// PriorityUpdateFrame is a PRIORITY_UPDATE frame as described in
|
||||
// https://www.rfc-editor.org/rfc/rfc9218.html#name-the-priority_update-frame.
|
||||
type PriorityUpdateFrame struct {
|
||||
FrameHeader
|
||||
Priority string
|
||||
PrioritizedStreamID uint32
|
||||
}
|
||||
|
||||
func parseRFC9218Priority(s string, canUseDefault bool) (p PriorityParam, ok bool) {
|
||||
p = defaultRFC9218Priority(canUseDefault)
|
||||
ok = httpsfv.ParseDictionary(s, func(key, val, _ string) {
|
||||
switch key {
|
||||
case "u":
|
||||
if u, ok := httpsfv.ParseInteger(val); ok && u >= 0 && u <= 7 {
|
||||
p.urgency = uint8(u)
|
||||
}
|
||||
case "i":
|
||||
if i, ok := httpsfv.ParseBoolean(val); ok {
|
||||
if i {
|
||||
p.incremental = 1
|
||||
} else {
|
||||
p.incremental = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
if !ok {
|
||||
return defaultRFC9218Priority(canUseDefault), ok
|
||||
}
|
||||
return p, true
|
||||
}
|
||||
|
||||
func parsePriorityUpdateFrame(_ *frameCache, fh FrameHeader, countError func(string), payload []byte) (Frame, error) {
|
||||
if fh.StreamID != 0 {
|
||||
countError("frame_priority_update_non_zero_stream")
|
||||
return nil, connError{ErrCodeProtocol, "PRIORITY_UPDATE frame with non-zero stream ID"}
|
||||
}
|
||||
if len(payload) < 4 {
|
||||
countError("frame_priority_update_bad_length")
|
||||
return nil, connError{ErrCodeFrameSize, fmt.Sprintf("PRIORITY_UPDATE frame payload size was %d; want at least 4", len(payload))}
|
||||
}
|
||||
v := binary.BigEndian.Uint32(payload[:4])
|
||||
streamID := v & 0x7fffffff // mask off high bit
|
||||
if streamID == 0 {
|
||||
countError("frame_priority_update_prioritizing_zero_stream")
|
||||
return nil, connError{ErrCodeProtocol, "PRIORITY_UPDATE frame with prioritized stream ID of zero"}
|
||||
}
|
||||
return &PriorityUpdateFrame{
|
||||
FrameHeader: fh,
|
||||
PrioritizedStreamID: streamID,
|
||||
Priority: string(payload[4:]),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WritePriorityUpdate writes a PRIORITY_UPDATE frame.
|
||||
//
|
||||
// It will perform exactly one Write to the underlying Writer.
|
||||
// It is the caller's responsibility to not call other Write methods concurrently.
|
||||
func (f *Framer) WritePriorityUpdate(streamID uint32, priority string) error {
|
||||
if !validStreamID(streamID) && !f.AllowIllegalWrites {
|
||||
return errStreamID
|
||||
}
|
||||
f.startWrite(FramePriorityUpdate, 0, 0)
|
||||
f.writeUint32(streamID)
|
||||
f.writeBytes([]byte(priority))
|
||||
return f.endWrite()
|
||||
}
|
||||
|
||||
// A RSTStreamFrame allows for abnormal termination of a stream.
|
||||
// See https://httpwg.org/specs/rfc7540.html#rfc.section.6.4
|
||||
type RSTStreamFrame struct {
|
||||
@@ -1519,6 +1648,23 @@ func (mh *MetaHeadersFrame) PseudoFields() []hpack.HeaderField {
|
||||
return mh.Fields
|
||||
}
|
||||
|
||||
func (mh *MetaHeadersFrame) rfc9218Priority(priorityAware bool) (p PriorityParam, priorityAwareAfter, hasIntermediary bool) {
|
||||
var s string
|
||||
for _, field := range mh.Fields {
|
||||
if field.Name == "priority" {
|
||||
s = field.Value
|
||||
priorityAware = true
|
||||
}
|
||||
if slices.Contains([]string{"via", "forwarded", "x-forwarded-for"}, field.Name) {
|
||||
hasIntermediary = true
|
||||
}
|
||||
}
|
||||
// No need to check for ok. parseRFC9218Priority will return a default
|
||||
// value if there is no priority field or if the field cannot be parsed.
|
||||
p, _ = parseRFC9218Priority(s, priorityAware && !hasIntermediary)
|
||||
return p, priorityAware, hasIntermediary
|
||||
}
|
||||
|
||||
func (mh *MetaHeadersFrame) checkPseudos() error {
|
||||
var isRequest, isResponse bool
|
||||
pf := mh.PseudoFields()
|
||||
|
||||
+2
@@ -169,6 +169,7 @@ const (
|
||||
SettingMaxFrameSize SettingID = 0x5
|
||||
SettingMaxHeaderListSize SettingID = 0x6
|
||||
SettingEnableConnectProtocol SettingID = 0x8
|
||||
SettingNoRFC7540Priorities SettingID = 0x9
|
||||
)
|
||||
|
||||
var settingName = map[SettingID]string{
|
||||
@@ -179,6 +180,7 @@ var settingName = map[SettingID]string{
|
||||
SettingMaxFrameSize: "MAX_FRAME_SIZE",
|
||||
SettingMaxHeaderListSize: "MAX_HEADER_LIST_SIZE",
|
||||
SettingEnableConnectProtocol: "ENABLE_CONNECT_PROTOCOL",
|
||||
SettingNoRFC7540Priorities: "NO_RFC7540_PRIORITIES",
|
||||
}
|
||||
|
||||
func (s SettingID) String() string {
|
||||
|
||||
+78
-8
@@ -472,10 +472,13 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon
|
||||
sc.conn.SetWriteDeadline(time.Time{})
|
||||
}
|
||||
|
||||
if s.NewWriteScheduler != nil {
|
||||
switch {
|
||||
case s.NewWriteScheduler != nil:
|
||||
sc.writeSched = s.NewWriteScheduler()
|
||||
} else {
|
||||
case clientPriorityDisabled(http1srv):
|
||||
sc.writeSched = newRoundRobinWriteScheduler()
|
||||
default:
|
||||
sc.writeSched = newPriorityWriteSchedulerRFC9218()
|
||||
}
|
||||
|
||||
// These start at the RFC-specified defaults. If there is a higher
|
||||
@@ -648,6 +651,23 @@ type serverConn struct {
|
||||
|
||||
// Used by startGracefulShutdown.
|
||||
shutdownOnce sync.Once
|
||||
|
||||
// Used for RFC 9218 prioritization.
|
||||
hasIntermediary bool // connection is done via an intermediary / proxy
|
||||
priorityAware bool // the client has sent priority signal, meaning that it is aware of it.
|
||||
}
|
||||
|
||||
func (sc *serverConn) writeSchedIgnoresRFC7540() bool {
|
||||
switch sc.writeSched.(type) {
|
||||
case *priorityWriteSchedulerRFC9218:
|
||||
return true
|
||||
case *randomWriteScheduler:
|
||||
return true
|
||||
case *roundRobinWriteScheduler:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *serverConn) maxHeaderListSize() uint32 {
|
||||
@@ -938,6 +958,9 @@ func (sc *serverConn) serve(conf http2Config) {
|
||||
if !disableExtendedConnectProtocol {
|
||||
settings = append(settings, Setting{SettingEnableConnectProtocol, 1})
|
||||
}
|
||||
if sc.writeSchedIgnoresRFC7540() {
|
||||
settings = append(settings, Setting{SettingNoRFC7540Priorities, 1})
|
||||
}
|
||||
sc.writeFrame(FrameWriteRequest{
|
||||
write: settings,
|
||||
})
|
||||
@@ -1623,6 +1646,8 @@ func (sc *serverConn) processFrame(f Frame) error {
|
||||
// A client cannot push. Thus, servers MUST treat the receipt of a PUSH_PROMISE
|
||||
// frame as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
|
||||
return sc.countError("push_promise", ConnectionError(ErrCodeProtocol))
|
||||
case *PriorityUpdateFrame:
|
||||
return sc.processPriorityUpdate(f)
|
||||
default:
|
||||
sc.vlogf("http2: server ignoring frame: %v", f.Header())
|
||||
return nil
|
||||
@@ -1803,6 +1828,10 @@ func (sc *serverConn) processSetting(s Setting) error {
|
||||
case SettingEnableConnectProtocol:
|
||||
// Receipt of this parameter by a server does not
|
||||
// have any impact
|
||||
case SettingNoRFC7540Priorities:
|
||||
if s.Val > 1 {
|
||||
return ConnectionError(ErrCodeProtocol)
|
||||
}
|
||||
default:
|
||||
// Unknown setting: "An endpoint that receives a SETTINGS
|
||||
// frame with any unknown or unsupported identifier MUST
|
||||
@@ -2073,13 +2102,33 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error {
|
||||
if f.StreamEnded() {
|
||||
initialState = stateHalfClosedRemote
|
||||
}
|
||||
st := sc.newStream(id, 0, initialState)
|
||||
|
||||
// We are handling two special cases here:
|
||||
// 1. When a request is sent via an intermediary, we force priority to be
|
||||
// u=3,i. This is essentially a round-robin behavior, and is done to ensure
|
||||
// fairness between, for example, multiple clients using the same proxy.
|
||||
// 2. Until a client has shown that it is aware of RFC 9218, we make its
|
||||
// streams non-incremental by default. This is done to preserve the
|
||||
// historical behavior of handling streams in a round-robin manner, rather
|
||||
// than one-by-one to completion.
|
||||
initialPriority := defaultRFC9218Priority(sc.priorityAware && !sc.hasIntermediary)
|
||||
if _, ok := sc.writeSched.(*priorityWriteSchedulerRFC9218); ok && !sc.hasIntermediary {
|
||||
headerPriority, priorityAware, hasIntermediary := f.rfc9218Priority(sc.priorityAware)
|
||||
initialPriority = headerPriority
|
||||
sc.hasIntermediary = hasIntermediary
|
||||
if priorityAware {
|
||||
sc.priorityAware = true
|
||||
}
|
||||
}
|
||||
st := sc.newStream(id, 0, initialState, initialPriority)
|
||||
|
||||
if f.HasPriority() {
|
||||
if err := sc.checkPriority(f.StreamID, f.Priority); err != nil {
|
||||
return err
|
||||
}
|
||||
sc.writeSched.AdjustStream(st.id, f.Priority)
|
||||
if !sc.writeSchedIgnoresRFC7540() {
|
||||
sc.writeSched.AdjustStream(st.id, f.Priority)
|
||||
}
|
||||
}
|
||||
|
||||
rw, req, err := sc.newWriterAndRequest(st, f)
|
||||
@@ -2120,7 +2169,7 @@ func (sc *serverConn) upgradeRequest(req *http.Request) {
|
||||
sc.serveG.check()
|
||||
id := uint32(1)
|
||||
sc.maxClientStreamID = id
|
||||
st := sc.newStream(id, 0, stateHalfClosedRemote)
|
||||
st := sc.newStream(id, 0, stateHalfClosedRemote, defaultRFC9218Priority(sc.priorityAware && !sc.hasIntermediary))
|
||||
st.reqTrailer = req.Trailer
|
||||
if st.reqTrailer != nil {
|
||||
st.trailer = make(http.Header)
|
||||
@@ -2185,11 +2234,32 @@ func (sc *serverConn) processPriority(f *PriorityFrame) error {
|
||||
if err := sc.checkPriority(f.StreamID, f.PriorityParam); err != nil {
|
||||
return err
|
||||
}
|
||||
// We need to avoid calling AdjustStream when using the RFC 9218 write
|
||||
// scheduler. Otherwise, incremental's zero value in PriorityParam will
|
||||
// unexpectedly make all streams non-incremental. This causes us to process
|
||||
// streams one-by-one to completion rather than doing it in a round-robin
|
||||
// manner (the historical behavior), which might be unexpected to users.
|
||||
if sc.writeSchedIgnoresRFC7540() {
|
||||
return nil
|
||||
}
|
||||
sc.writeSched.AdjustStream(f.StreamID, f.PriorityParam)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sc *serverConn) newStream(id, pusherID uint32, state streamState) *stream {
|
||||
func (sc *serverConn) processPriorityUpdate(f *PriorityUpdateFrame) error {
|
||||
sc.priorityAware = true
|
||||
if _, ok := sc.writeSched.(*priorityWriteSchedulerRFC9218); !ok {
|
||||
return nil
|
||||
}
|
||||
p, ok := parseRFC9218Priority(f.Priority, sc.priorityAware)
|
||||
if !ok {
|
||||
return sc.countError("unparsable_priority_update", streamError(f.PrioritizedStreamID, ErrCodeProtocol))
|
||||
}
|
||||
sc.writeSched.AdjustStream(f.PrioritizedStreamID, p)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sc *serverConn) newStream(id, pusherID uint32, state streamState, priority PriorityParam) *stream {
|
||||
sc.serveG.check()
|
||||
if id == 0 {
|
||||
panic("internal error: cannot create stream with id 0")
|
||||
@@ -2212,7 +2282,7 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState) *stream
|
||||
}
|
||||
|
||||
sc.streams[id] = st
|
||||
sc.writeSched.OpenStream(st.id, OpenStreamOptions{PusherID: pusherID})
|
||||
sc.writeSched.OpenStream(st.id, OpenStreamOptions{PusherID: pusherID, priority: priority})
|
||||
if st.isPushed() {
|
||||
sc.curPushedStreams++
|
||||
} else {
|
||||
@@ -3218,7 +3288,7 @@ func (sc *serverConn) startPush(msg *startPushRequest) {
|
||||
// transition to "half closed (remote)" after sending the initial HEADERS, but
|
||||
// we start in "half closed (remote)" for simplicity.
|
||||
// See further comments at the definition of stateHalfClosedRemote.
|
||||
promised := sc.newStream(promisedID, msg.parent.id, stateHalfClosedRemote)
|
||||
promised := sc.newStream(promisedID, msg.parent.id, stateHalfClosedRemote, defaultRFC9218Priority(sc.priorityAware && !sc.hasIntermediary))
|
||||
rw, req, err := sc.newWriterAndRequestNoBody(promised, httpcommon.ServerRequestParam{
|
||||
Method: msg.method,
|
||||
Scheme: msg.url.Scheme,
|
||||
|
||||
+240
-31
@@ -9,6 +9,7 @@ package http2
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
@@ -375,11 +376,24 @@ type ClientConn struct {
|
||||
// completely unresponsive connection.
|
||||
pendingResets int
|
||||
|
||||
// readBeforeStreamID is the smallest stream ID that has not been followed by
|
||||
// a frame read from the peer. We use this to determine when a request may
|
||||
// have been sent to a completely unresponsive connection:
|
||||
// If the request ID is less than readBeforeStreamID, then we have had some
|
||||
// indication of life on the connection since sending the request.
|
||||
readBeforeStreamID uint32
|
||||
|
||||
// reqHeaderMu is a 1-element semaphore channel controlling access to sending new requests.
|
||||
// Write to reqHeaderMu to lock it, read from it to unlock.
|
||||
// Lock reqmu BEFORE mu or wmu.
|
||||
reqHeaderMu chan struct{}
|
||||
|
||||
// internalStateHook reports state changes back to the net/http.ClientConn.
|
||||
// Note that this is different from the user state hook registered by
|
||||
// net/http.ClientConn.SetStateHook: The internal hook calls ClientConn,
|
||||
// which calls the user hook.
|
||||
internalStateHook func()
|
||||
|
||||
// wmu is held while writing.
|
||||
// Acquire BEFORE mu when holding both, to avoid blocking mu on network writes.
|
||||
// Only acquire both at the same time when changing peer settings.
|
||||
@@ -709,7 +723,7 @@ func canRetryError(err error) bool {
|
||||
|
||||
func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*ClientConn, error) {
|
||||
if t.transportTestHooks != nil {
|
||||
return t.newClientConn(nil, singleUse)
|
||||
return t.newClientConn(nil, singleUse, nil)
|
||||
}
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
@@ -719,7 +733,7 @@ func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse b
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t.newClientConn(tconn, singleUse)
|
||||
return t.newClientConn(tconn, singleUse, nil)
|
||||
}
|
||||
|
||||
func (t *Transport) newTLSConfig(host string) *tls.Config {
|
||||
@@ -771,10 +785,10 @@ func (t *Transport) expectContinueTimeout() time.Duration {
|
||||
}
|
||||
|
||||
func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) {
|
||||
return t.newClientConn(c, t.disableKeepAlives())
|
||||
return t.newClientConn(c, t.disableKeepAlives(), nil)
|
||||
}
|
||||
|
||||
func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, error) {
|
||||
func (t *Transport) newClientConn(c net.Conn, singleUse bool, internalStateHook func()) (*ClientConn, error) {
|
||||
conf := configFromTransport(t)
|
||||
cc := &ClientConn{
|
||||
t: t,
|
||||
@@ -796,6 +810,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
|
||||
pings: make(map[[8]byte]chan struct{}),
|
||||
reqHeaderMu: make(chan struct{}, 1),
|
||||
lastActive: time.Now(),
|
||||
internalStateHook: internalStateHook,
|
||||
}
|
||||
if t.transportTestHooks != nil {
|
||||
t.transportTestHooks.newclientconn(cc)
|
||||
@@ -1036,10 +1051,7 @@ func (cc *ClientConn) idleStateLocked() (st clientConnIdleState) {
|
||||
maxConcurrentOkay = cc.currentRequestCountLocked() < int(cc.maxConcurrentStreams)
|
||||
}
|
||||
|
||||
st.canTakeNewRequest = cc.goAway == nil && !cc.closed && !cc.closing && maxConcurrentOkay &&
|
||||
!cc.doNotReuse &&
|
||||
int64(cc.nextStreamID)+2*int64(cc.pendingRequests) < math.MaxInt32 &&
|
||||
!cc.tooIdleLocked()
|
||||
st.canTakeNewRequest = maxConcurrentOkay && cc.isUsableLocked()
|
||||
|
||||
// If this connection has never been used for a request and is closed,
|
||||
// then let it take a request (which will fail).
|
||||
@@ -1055,6 +1067,31 @@ func (cc *ClientConn) idleStateLocked() (st clientConnIdleState) {
|
||||
return
|
||||
}
|
||||
|
||||
func (cc *ClientConn) isUsableLocked() bool {
|
||||
return cc.goAway == nil &&
|
||||
!cc.closed &&
|
||||
!cc.closing &&
|
||||
!cc.doNotReuse &&
|
||||
int64(cc.nextStreamID)+2*int64(cc.pendingRequests) < math.MaxInt32 &&
|
||||
!cc.tooIdleLocked()
|
||||
}
|
||||
|
||||
// canReserveLocked reports whether a net/http.ClientConn can reserve a slot on this conn.
|
||||
//
|
||||
// This follows slightly different rules than clientConnIdleState.canTakeNewRequest.
|
||||
// We only permit reservations up to the conn's concurrency limit.
|
||||
// This differs from ClientConn.ReserveNewRequest, which permits reservations
|
||||
// past the limit when StrictMaxConcurrentStreams is set.
|
||||
func (cc *ClientConn) canReserveLocked() bool {
|
||||
if cc.currentRequestCountLocked() >= int(cc.maxConcurrentStreams) {
|
||||
return false
|
||||
}
|
||||
if !cc.isUsableLocked() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// currentRequestCountLocked reports the number of concurrency slots currently in use,
|
||||
// including active streams, reserved slots, and reset streams waiting for acknowledgement.
|
||||
func (cc *ClientConn) currentRequestCountLocked() int {
|
||||
@@ -1066,6 +1103,14 @@ func (cc *ClientConn) canTakeNewRequestLocked() bool {
|
||||
return st.canTakeNewRequest
|
||||
}
|
||||
|
||||
// availableLocked reports the number of concurrency slots available.
|
||||
func (cc *ClientConn) availableLocked() int {
|
||||
if !cc.canTakeNewRequestLocked() {
|
||||
return 0
|
||||
}
|
||||
return max(0, int(cc.maxConcurrentStreams)-cc.currentRequestCountLocked())
|
||||
}
|
||||
|
||||
// tooIdleLocked reports whether this connection has been been sitting idle
|
||||
// for too much wall time.
|
||||
func (cc *ClientConn) tooIdleLocked() bool {
|
||||
@@ -1090,6 +1135,7 @@ func (cc *ClientConn) closeConn() {
|
||||
t := time.AfterFunc(250*time.Millisecond, cc.forceCloseConn)
|
||||
defer t.Stop()
|
||||
cc.tconn.Close()
|
||||
cc.maybeCallStateHook()
|
||||
}
|
||||
|
||||
// A tls.Conn.Close can hang for a long time if the peer is unresponsive.
|
||||
@@ -1615,6 +1661,8 @@ func (cs *clientStream) cleanupWriteRequest(err error) {
|
||||
}
|
||||
bodyClosed := cs.reqBodyClosed
|
||||
closeOnIdle := cc.singleUse || cc.doNotReuse || cc.t.disableKeepAlives() || cc.goAway != nil
|
||||
// Have we read any frames from the connection since sending this request?
|
||||
readSinceStream := cc.readBeforeStreamID > cs.ID
|
||||
cc.mu.Unlock()
|
||||
if mustCloseBody {
|
||||
cs.reqBody.Close()
|
||||
@@ -1646,8 +1694,10 @@ func (cs *clientStream) cleanupWriteRequest(err error) {
|
||||
//
|
||||
// This could be due to the server becoming unresponsive.
|
||||
// To avoid sending too many requests on a dead connection,
|
||||
// we let the request continue to consume a concurrency slot
|
||||
// until we can confirm the server is still responding.
|
||||
// if we haven't read any frames from the connection since
|
||||
// sending this request, we let it continue to consume
|
||||
// a concurrency slot until we can confirm the server is
|
||||
// still responding.
|
||||
// We do this by sending a PING frame along with the RST_STREAM
|
||||
// (unless a ping is already in flight).
|
||||
//
|
||||
@@ -1658,7 +1708,7 @@ func (cs *clientStream) cleanupWriteRequest(err error) {
|
||||
// because it's short lived and will probably be closed before
|
||||
// we get the ping response.
|
||||
ping := false
|
||||
if !closeOnIdle {
|
||||
if !closeOnIdle && !readSinceStream {
|
||||
cc.mu.Lock()
|
||||
// rstStreamPingsBlocked works around a gRPC behavior:
|
||||
// see comment on the field for details.
|
||||
@@ -1692,6 +1742,7 @@ func (cs *clientStream) cleanupWriteRequest(err error) {
|
||||
}
|
||||
|
||||
close(cs.donec)
|
||||
cc.maybeCallStateHook()
|
||||
}
|
||||
|
||||
// awaitOpenSlotForStreamLocked waits until len(streams) < maxConcurrentStreams.
|
||||
@@ -2728,6 +2779,11 @@ func (rl *clientConnReadLoop) endStreamError(cs *clientStream, err error) {
|
||||
cs.abortStream(err)
|
||||
}
|
||||
|
||||
func (rl *clientConnReadLoop) endStreamErrorLocked(cs *clientStream, err error) {
|
||||
cs.readAborted = true
|
||||
cs.abortStreamLocked(err)
|
||||
}
|
||||
|
||||
// Constants passed to streamByID for documentation purposes.
|
||||
const (
|
||||
headerOrDataFrame = true
|
||||
@@ -2744,6 +2800,7 @@ func (rl *clientConnReadLoop) streamByID(id uint32, headerOrData bool) *clientSt
|
||||
// See comment on ClientConn.rstStreamPingsBlocked for details.
|
||||
rl.cc.rstStreamPingsBlocked = false
|
||||
}
|
||||
rl.cc.readBeforeStreamID = rl.cc.nextStreamID
|
||||
cs := rl.cc.streams[id]
|
||||
if cs != nil && !cs.readAborted {
|
||||
return cs
|
||||
@@ -2794,6 +2851,7 @@ func (rl *clientConnReadLoop) processSettings(f *SettingsFrame) error {
|
||||
|
||||
func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
|
||||
cc := rl.cc
|
||||
defer cc.maybeCallStateHook()
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
|
||||
@@ -2893,7 +2951,7 @@ func (rl *clientConnReadLoop) processWindowUpdate(f *WindowUpdateFrame) error {
|
||||
if !fl.add(int32(f.Increment)) {
|
||||
// For stream, the sender sends RST_STREAM with an error code of FLOW_CONTROL_ERROR
|
||||
if cs != nil {
|
||||
rl.endStreamError(cs, StreamError{
|
||||
rl.endStreamErrorLocked(cs, StreamError{
|
||||
StreamID: f.StreamID,
|
||||
Code: ErrCodeFlowControl,
|
||||
})
|
||||
@@ -2974,6 +3032,7 @@ func (cc *ClientConn) Ping(ctx context.Context) error {
|
||||
func (rl *clientConnReadLoop) processPing(f *PingFrame) error {
|
||||
if f.IsAck() {
|
||||
cc := rl.cc
|
||||
defer cc.maybeCallStateHook()
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
// If ack, notify listener if any
|
||||
@@ -3076,35 +3135,102 @@ type erringRoundTripper struct{ err error }
|
||||
func (rt erringRoundTripper) RoundTripErr() error { return rt.err }
|
||||
func (rt erringRoundTripper) RoundTrip(*http.Request) (*http.Response, error) { return nil, rt.err }
|
||||
|
||||
var errConcurrentReadOnResBody = errors.New("http2: concurrent read on response body")
|
||||
|
||||
// gzipReader wraps a response body so it can lazily
|
||||
// call gzip.NewReader on the first call to Read
|
||||
// get gzip.Reader from the pool on the first call to Read.
|
||||
// After Close is called it puts gzip.Reader to the pool immediately
|
||||
// if there is no Read in progress or later when Read completes.
|
||||
type gzipReader struct {
|
||||
_ incomparable
|
||||
body io.ReadCloser // underlying Response.Body
|
||||
zr *gzip.Reader // lazily-initialized gzip reader
|
||||
zerr error // sticky error
|
||||
mu sync.Mutex // guards zr and zerr
|
||||
zr *gzip.Reader // stores gzip reader from the pool between reads
|
||||
zerr error // sticky gzip reader init error or sentinel value to detect concurrent read and read after close
|
||||
}
|
||||
|
||||
type eofReader struct{}
|
||||
|
||||
func (eofReader) Read([]byte) (int, error) { return 0, io.EOF }
|
||||
func (eofReader) ReadByte() (byte, error) { return 0, io.EOF }
|
||||
|
||||
var gzipPool = sync.Pool{New: func() any { return new(gzip.Reader) }}
|
||||
|
||||
// gzipPoolGet gets a gzip.Reader from the pool and resets it to read from r.
|
||||
func gzipPoolGet(r io.Reader) (*gzip.Reader, error) {
|
||||
zr := gzipPool.Get().(*gzip.Reader)
|
||||
if err := zr.Reset(r); err != nil {
|
||||
gzipPoolPut(zr)
|
||||
return nil, err
|
||||
}
|
||||
return zr, nil
|
||||
}
|
||||
|
||||
// gzipPoolPut puts a gzip.Reader back into the pool.
|
||||
func gzipPoolPut(zr *gzip.Reader) {
|
||||
// Reset will allocate bufio.Reader if we pass it anything
|
||||
// other than a flate.Reader, so ensure that it's getting one.
|
||||
var r flate.Reader = eofReader{}
|
||||
zr.Reset(r)
|
||||
gzipPool.Put(zr)
|
||||
}
|
||||
|
||||
// acquire returns a gzip.Reader for reading response body.
|
||||
// The reader must be released after use.
|
||||
func (gz *gzipReader) acquire() (*gzip.Reader, error) {
|
||||
gz.mu.Lock()
|
||||
defer gz.mu.Unlock()
|
||||
if gz.zerr != nil {
|
||||
return nil, gz.zerr
|
||||
}
|
||||
if gz.zr == nil {
|
||||
gz.zr, gz.zerr = gzipPoolGet(gz.body)
|
||||
if gz.zerr != nil {
|
||||
return nil, gz.zerr
|
||||
}
|
||||
}
|
||||
ret := gz.zr
|
||||
gz.zr, gz.zerr = nil, errConcurrentReadOnResBody
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// release returns the gzip.Reader to the pool if Close was called during Read.
|
||||
func (gz *gzipReader) release(zr *gzip.Reader) {
|
||||
gz.mu.Lock()
|
||||
defer gz.mu.Unlock()
|
||||
if gz.zerr == errConcurrentReadOnResBody {
|
||||
gz.zr, gz.zerr = zr, nil
|
||||
} else { // fs.ErrClosed
|
||||
gzipPoolPut(zr)
|
||||
}
|
||||
}
|
||||
|
||||
// close returns the gzip.Reader to the pool immediately or
|
||||
// signals release to do so after Read completes.
|
||||
func (gz *gzipReader) close() {
|
||||
gz.mu.Lock()
|
||||
defer gz.mu.Unlock()
|
||||
if gz.zerr == nil && gz.zr != nil {
|
||||
gzipPoolPut(gz.zr)
|
||||
gz.zr = nil
|
||||
}
|
||||
gz.zerr = fs.ErrClosed
|
||||
}
|
||||
|
||||
func (gz *gzipReader) Read(p []byte) (n int, err error) {
|
||||
if gz.zerr != nil {
|
||||
return 0, gz.zerr
|
||||
zr, err := gz.acquire()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if gz.zr == nil {
|
||||
gz.zr, err = gzip.NewReader(gz.body)
|
||||
if err != nil {
|
||||
gz.zerr = err
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return gz.zr.Read(p)
|
||||
defer gz.release(zr)
|
||||
|
||||
return zr.Read(p)
|
||||
}
|
||||
|
||||
func (gz *gzipReader) Close() error {
|
||||
if err := gz.body.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
gz.zerr = fs.ErrClosed
|
||||
return nil
|
||||
gz.close()
|
||||
|
||||
return gz.body.Close()
|
||||
}
|
||||
|
||||
type errorReader struct{ err error }
|
||||
@@ -3130,9 +3256,13 @@ func registerHTTPSProtocol(t *http.Transport, rt noDialH2RoundTripper) (err erro
|
||||
}
|
||||
|
||||
// noDialH2RoundTripper is a RoundTripper which only tries to complete the request
|
||||
// if there's already has a cached connection to the host.
|
||||
// if there's already a cached connection to the host.
|
||||
// (The field is exported so it can be accessed via reflect from net/http; tested
|
||||
// by TestNoDialH2RoundTripperType)
|
||||
//
|
||||
// A noDialH2RoundTripper is registered with http1.Transport.RegisterProtocol,
|
||||
// and the http1.Transport can use type assertions to call non-RoundTrip methods on it.
|
||||
// This lets us expose, for example, NewClientConn to net/http.
|
||||
type noDialH2RoundTripper struct{ *Transport }
|
||||
|
||||
func (rt noDialH2RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
@@ -3143,6 +3273,85 @@ func (rt noDialH2RoundTripper) RoundTrip(req *http.Request) (*http.Response, err
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (rt noDialH2RoundTripper) NewClientConn(conn net.Conn, internalStateHook func()) (http.RoundTripper, error) {
|
||||
tr := rt.Transport
|
||||
cc, err := tr.newClientConn(conn, tr.disableKeepAlives(), internalStateHook)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// RoundTrip should block when the conn is at its concurrency limit,
|
||||
// not return an error. Setting strictMaxConcurrentStreams enables this.
|
||||
cc.strictMaxConcurrentStreams = true
|
||||
|
||||
return netHTTPClientConn{cc}, nil
|
||||
}
|
||||
|
||||
// netHTTPClientConn wraps ClientConn and implements the interface net/http expects from
|
||||
// the RoundTripper returned by NewClientConn.
|
||||
type netHTTPClientConn struct {
|
||||
cc *ClientConn
|
||||
}
|
||||
|
||||
func (cc netHTTPClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return cc.cc.RoundTrip(req)
|
||||
}
|
||||
|
||||
func (cc netHTTPClientConn) Close() error {
|
||||
return cc.cc.Close()
|
||||
}
|
||||
|
||||
func (cc netHTTPClientConn) Err() error {
|
||||
cc.cc.mu.Lock()
|
||||
defer cc.cc.mu.Unlock()
|
||||
if cc.cc.closed {
|
||||
return errors.New("connection closed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cc netHTTPClientConn) Reserve() error {
|
||||
defer cc.cc.maybeCallStateHook()
|
||||
cc.cc.mu.Lock()
|
||||
defer cc.cc.mu.Unlock()
|
||||
if !cc.cc.canReserveLocked() {
|
||||
return errors.New("connection is unavailable")
|
||||
}
|
||||
cc.cc.streamsReserved++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cc netHTTPClientConn) Release() {
|
||||
defer cc.cc.maybeCallStateHook()
|
||||
cc.cc.mu.Lock()
|
||||
defer cc.cc.mu.Unlock()
|
||||
// We don't complain if streamsReserved is 0.
|
||||
//
|
||||
// This is consistent with RoundTrip: both Release and RoundTrip will
|
||||
// consume a reservation iff one exists.
|
||||
if cc.cc.streamsReserved > 0 {
|
||||
cc.cc.streamsReserved--
|
||||
}
|
||||
}
|
||||
|
||||
func (cc netHTTPClientConn) Available() int {
|
||||
cc.cc.mu.Lock()
|
||||
defer cc.cc.mu.Unlock()
|
||||
return cc.cc.availableLocked()
|
||||
}
|
||||
|
||||
func (cc netHTTPClientConn) InFlight() int {
|
||||
cc.cc.mu.Lock()
|
||||
defer cc.cc.mu.Unlock()
|
||||
return cc.cc.currentRequestCountLocked()
|
||||
}
|
||||
|
||||
func (cc *ClientConn) maybeCallStateHook() {
|
||||
if cc.internalStateHook != nil {
|
||||
cc.internalStateHook()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Transport) idleConnTimeout() time.Duration {
|
||||
// to keep things backwards compatible, we use non-zero values of
|
||||
// IdleConnTimeout, followed by using the IdleConnTimeout on the underlying
|
||||
|
||||
+50
-15
@@ -185,45 +185,75 @@ func (wr *FrameWriteRequest) replyToWriter(err error) {
|
||||
}
|
||||
|
||||
// writeQueue is used by implementations of WriteScheduler.
|
||||
//
|
||||
// Each writeQueue contains a queue of FrameWriteRequests, meant to store all
|
||||
// FrameWriteRequests associated with a given stream. This is implemented as a
|
||||
// two-stage queue: currQueue[currPos:] and nextQueue. Removing an item is done
|
||||
// by incrementing currPos of currQueue. Adding an item is done by appending it
|
||||
// to the nextQueue. If currQueue is empty when trying to remove an item, we
|
||||
// can swap currQueue and nextQueue to remedy the situation.
|
||||
// This two-stage queue is analogous to the use of two lists in Okasaki's
|
||||
// purely functional queue but without the overhead of reversing the list when
|
||||
// swapping stages.
|
||||
//
|
||||
// writeQueue also contains prev and next, this can be used by implementations
|
||||
// of WriteScheduler to construct data structures that represent the order of
|
||||
// writing between different streams (e.g. circular linked list).
|
||||
type writeQueue struct {
|
||||
s []FrameWriteRequest
|
||||
currQueue []FrameWriteRequest
|
||||
nextQueue []FrameWriteRequest
|
||||
currPos int
|
||||
|
||||
prev, next *writeQueue
|
||||
}
|
||||
|
||||
func (q *writeQueue) empty() bool { return len(q.s) == 0 }
|
||||
func (q *writeQueue) empty() bool {
|
||||
return (len(q.currQueue) - q.currPos + len(q.nextQueue)) == 0
|
||||
}
|
||||
|
||||
func (q *writeQueue) push(wr FrameWriteRequest) {
|
||||
q.s = append(q.s, wr)
|
||||
q.nextQueue = append(q.nextQueue, wr)
|
||||
}
|
||||
|
||||
func (q *writeQueue) shift() FrameWriteRequest {
|
||||
if len(q.s) == 0 {
|
||||
if q.empty() {
|
||||
panic("invalid use of queue")
|
||||
}
|
||||
wr := q.s[0]
|
||||
// TODO: less copy-happy queue.
|
||||
copy(q.s, q.s[1:])
|
||||
q.s[len(q.s)-1] = FrameWriteRequest{}
|
||||
q.s = q.s[:len(q.s)-1]
|
||||
if q.currPos >= len(q.currQueue) {
|
||||
q.currQueue, q.currPos, q.nextQueue = q.nextQueue, 0, q.currQueue[:0]
|
||||
}
|
||||
wr := q.currQueue[q.currPos]
|
||||
q.currQueue[q.currPos] = FrameWriteRequest{}
|
||||
q.currPos++
|
||||
return wr
|
||||
}
|
||||
|
||||
func (q *writeQueue) peek() *FrameWriteRequest {
|
||||
if q.currPos < len(q.currQueue) {
|
||||
return &q.currQueue[q.currPos]
|
||||
}
|
||||
if len(q.nextQueue) > 0 {
|
||||
return &q.nextQueue[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// consume consumes up to n bytes from q.s[0]. If the frame is
|
||||
// entirely consumed, it is removed from the queue. If the frame
|
||||
// is partially consumed, the frame is kept with the consumed
|
||||
// bytes removed. Returns true iff any bytes were consumed.
|
||||
func (q *writeQueue) consume(n int32) (FrameWriteRequest, bool) {
|
||||
if len(q.s) == 0 {
|
||||
if q.empty() {
|
||||
return FrameWriteRequest{}, false
|
||||
}
|
||||
consumed, rest, numresult := q.s[0].Consume(n)
|
||||
consumed, rest, numresult := q.peek().Consume(n)
|
||||
switch numresult {
|
||||
case 0:
|
||||
return FrameWriteRequest{}, false
|
||||
case 1:
|
||||
q.shift()
|
||||
case 2:
|
||||
q.s[0] = rest
|
||||
*q.peek() = rest
|
||||
}
|
||||
return consumed, true
|
||||
}
|
||||
@@ -232,10 +262,15 @@ type writeQueuePool []*writeQueue
|
||||
|
||||
// put inserts an unused writeQueue into the pool.
|
||||
func (p *writeQueuePool) put(q *writeQueue) {
|
||||
for i := range q.s {
|
||||
q.s[i] = FrameWriteRequest{}
|
||||
for i := range q.currQueue {
|
||||
q.currQueue[i] = FrameWriteRequest{}
|
||||
}
|
||||
q.s = q.s[:0]
|
||||
for i := range q.nextQueue {
|
||||
q.nextQueue[i] = FrameWriteRequest{}
|
||||
}
|
||||
q.currQueue = q.currQueue[:0]
|
||||
q.nextQueue = q.nextQueue[:0]
|
||||
q.currPos = 0
|
||||
*p = append(*p, q)
|
||||
}
|
||||
|
||||
|
||||
+6
-3
@@ -56,6 +56,10 @@ type PriorityWriteSchedulerConfig struct {
|
||||
// frames by following HTTP/2 priorities as described in RFC 7540 Section 5.3.
|
||||
// If cfg is nil, default options are used.
|
||||
func NewPriorityWriteScheduler(cfg *PriorityWriteSchedulerConfig) WriteScheduler {
|
||||
return newPriorityWriteSchedulerRFC7540(cfg)
|
||||
}
|
||||
|
||||
func newPriorityWriteSchedulerRFC7540(cfg *PriorityWriteSchedulerConfig) WriteScheduler {
|
||||
if cfg == nil {
|
||||
// For justification of these defaults, see:
|
||||
// https://docs.google.com/document/d/1oLhNg1skaWD4_DtaoCxdSRN5erEXrH-KnLrMwEpOtFY
|
||||
@@ -214,8 +218,8 @@ func (z sortPriorityNodeSiblingsRFC7540) Swap(i, k int) { z[i], z[k] = z[k], z[i
|
||||
func (z sortPriorityNodeSiblingsRFC7540) Less(i, k int) bool {
|
||||
// Prefer the subtree that has sent fewer bytes relative to its weight.
|
||||
// See sections 5.3.2 and 5.3.4.
|
||||
wi, bi := float64(z[i].weight+1), float64(z[i].subtreeBytes)
|
||||
wk, bk := float64(z[k].weight+1), float64(z[k].subtreeBytes)
|
||||
wi, bi := float64(z[i].weight)+1, float64(z[i].subtreeBytes)
|
||||
wk, bk := float64(z[k].weight)+1, float64(z[k].subtreeBytes)
|
||||
if bi == 0 && bk == 0 {
|
||||
return wi >= wk
|
||||
}
|
||||
@@ -302,7 +306,6 @@ func (ws *priorityWriteSchedulerRFC7540) CloseStream(streamID uint32) {
|
||||
|
||||
q := n.q
|
||||
ws.queuePool.put(&q)
|
||||
n.q.s = nil
|
||||
if ws.maxClosedNodesInTree > 0 {
|
||||
ws.addClosedOrIdleNode(&ws.closedNodes, ws.maxClosedNodesInTree, n)
|
||||
} else {
|
||||
|
||||
Generated
Vendored
+16
-1
@@ -37,9 +37,18 @@ type priorityWriteSchedulerRFC9218 struct {
|
||||
// incremental streams or not, when urgency is the same in a given Pop()
|
||||
// call.
|
||||
prioritizeIncremental bool
|
||||
|
||||
// priorityUpdateBuf is used to buffer the most recent PRIORITY_UPDATE we
|
||||
// receive per https://www.rfc-editor.org/rfc/rfc9218.html#name-the-priority_update-frame.
|
||||
priorityUpdateBuf struct {
|
||||
// streamID being 0 means that the buffer is empty. This is a safe
|
||||
// assumption as PRIORITY_UPDATE for stream 0 is a PROTOCOL_ERROR.
|
||||
streamID uint32
|
||||
priority PriorityParam
|
||||
}
|
||||
}
|
||||
|
||||
func newPriorityWriteSchedulerRFC9128() WriteScheduler {
|
||||
func newPriorityWriteSchedulerRFC9218() WriteScheduler {
|
||||
ws := &priorityWriteSchedulerRFC9218{
|
||||
streams: make(map[uint32]streamMetadata),
|
||||
}
|
||||
@@ -50,6 +59,10 @@ func (ws *priorityWriteSchedulerRFC9218) OpenStream(streamID uint32, opt OpenStr
|
||||
if ws.streams[streamID].location != nil {
|
||||
panic(fmt.Errorf("stream %d already opened", streamID))
|
||||
}
|
||||
if streamID == ws.priorityUpdateBuf.streamID {
|
||||
ws.priorityUpdateBuf.streamID = 0
|
||||
opt.priority = ws.priorityUpdateBuf.priority
|
||||
}
|
||||
q := ws.queuePool.get()
|
||||
ws.streams[streamID] = streamMetadata{
|
||||
location: q,
|
||||
@@ -95,6 +108,8 @@ func (ws *priorityWriteSchedulerRFC9218) AdjustStream(streamID uint32, priority
|
||||
metadata := ws.streams[streamID]
|
||||
q, u, i := metadata.location, metadata.priority.urgency, metadata.priority.incremental
|
||||
if q == nil {
|
||||
ws.priorityUpdateBuf.streamID = streamID
|
||||
ws.priorityUpdateBuf.priority = priority
|
||||
return
|
||||
}
|
||||
|
||||
+665
@@ -0,0 +1,665 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package httpsfv provides functionality for dealing with HTTP Structured
|
||||
// Field Values.
|
||||
package httpsfv
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func isLCAlpha(b byte) bool {
|
||||
return (b >= 'a' && b <= 'z')
|
||||
}
|
||||
|
||||
func isAlpha(b byte) bool {
|
||||
return isLCAlpha(b) || (b >= 'A' && b <= 'Z')
|
||||
}
|
||||
|
||||
func isDigit(b byte) bool {
|
||||
return b >= '0' && b <= '9'
|
||||
}
|
||||
|
||||
func isVChar(b byte) bool {
|
||||
return b >= 0x21 && b <= 0x7e
|
||||
}
|
||||
|
||||
func isSP(b byte) bool {
|
||||
return b == 0x20
|
||||
}
|
||||
|
||||
func isTChar(b byte) bool {
|
||||
if isAlpha(b) || isDigit(b) {
|
||||
return true
|
||||
}
|
||||
return slices.Contains([]byte{'!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', '`', '|', '~'}, b)
|
||||
}
|
||||
|
||||
func countLeftWhitespace(s string) int {
|
||||
i := 0
|
||||
for _, ch := range []byte(s) {
|
||||
if ch != ' ' && ch != '\t' {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc4648#section-8.
|
||||
func decOctetHex(ch1, ch2 byte) (ch byte, ok bool) {
|
||||
decBase16 := func(in byte) (out byte, ok bool) {
|
||||
if !isDigit(in) && !(in >= 'a' && in <= 'f') {
|
||||
return 0, false
|
||||
}
|
||||
if isDigit(in) {
|
||||
return in - '0', true
|
||||
}
|
||||
return in - 'a' + 10, true
|
||||
}
|
||||
|
||||
if ch1, ok = decBase16(ch1); !ok {
|
||||
return 0, ok
|
||||
}
|
||||
if ch2, ok = decBase16(ch2); !ok {
|
||||
return 0, ok
|
||||
}
|
||||
return ch1<<4 | ch2, true
|
||||
}
|
||||
|
||||
// ParseList parses a list from a given HTTP Structured Field Values.
|
||||
//
|
||||
// Given an HTTP SFV string that represents a list, it will call the given
|
||||
// function using each of the members and parameters contained in the list.
|
||||
// This allows the caller to extract information out of the list.
|
||||
//
|
||||
// This function will return once it encounters the end of the string, or
|
||||
// something that is not a list. If it cannot consume the entire given
|
||||
// string, the ok value returned will be false.
|
||||
//
|
||||
// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-list.
|
||||
func ParseList(s string, f func(member, param string)) (ok bool) {
|
||||
for len(s) != 0 {
|
||||
var member, param string
|
||||
if len(s) != 0 && s[0] == '(' {
|
||||
if member, s, ok = consumeBareInnerList(s, nil); !ok {
|
||||
return ok
|
||||
}
|
||||
} else {
|
||||
if member, s, ok = consumeBareItem(s); !ok {
|
||||
return ok
|
||||
}
|
||||
}
|
||||
if param, s, ok = consumeParameter(s, nil); !ok {
|
||||
return ok
|
||||
}
|
||||
if f != nil {
|
||||
f(member, param)
|
||||
}
|
||||
|
||||
s = s[countLeftWhitespace(s):]
|
||||
if len(s) == 0 {
|
||||
break
|
||||
}
|
||||
if s[0] != ',' {
|
||||
return false
|
||||
}
|
||||
s = s[1:]
|
||||
s = s[countLeftWhitespace(s):]
|
||||
if len(s) == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// consumeBareInnerList consumes an inner list
|
||||
// (https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-inner-list),
|
||||
// except for the inner list's top-most parameter.
|
||||
// For example, given `(a;b c;d);e`, it will consume only `(a;b c;d)`.
|
||||
func consumeBareInnerList(s string, f func(bareItem, param string)) (consumed, rest string, ok bool) {
|
||||
if len(s) == 0 || s[0] != '(' {
|
||||
return "", s, false
|
||||
}
|
||||
rest = s[1:]
|
||||
for len(rest) != 0 {
|
||||
var bareItem, param string
|
||||
rest = rest[countLeftWhitespace(rest):]
|
||||
if len(rest) != 0 && rest[0] == ')' {
|
||||
rest = rest[1:]
|
||||
break
|
||||
}
|
||||
if bareItem, rest, ok = consumeBareItem(rest); !ok {
|
||||
return "", s, ok
|
||||
}
|
||||
if param, rest, ok = consumeParameter(rest, nil); !ok {
|
||||
return "", s, ok
|
||||
}
|
||||
if len(rest) == 0 || (rest[0] != ')' && !isSP(rest[0])) {
|
||||
return "", s, false
|
||||
}
|
||||
if f != nil {
|
||||
f(bareItem, param)
|
||||
}
|
||||
}
|
||||
return s[:len(s)-len(rest)], rest, true
|
||||
}
|
||||
|
||||
// ParseBareInnerList parses a bare inner list from a given HTTP Structured
|
||||
// Field Values.
|
||||
//
|
||||
// We define a bare inner list as an inner list
|
||||
// (https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-inner-list),
|
||||
// without the top-most parameter of the inner list. For example, given the
|
||||
// inner list `(a;b c;d);e`, the bare inner list would be `(a;b c;d)`.
|
||||
//
|
||||
// Given an HTTP SFV string that represents a bare inner list, it will call the
|
||||
// given function using each of the bare item and parameter within the bare
|
||||
// inner list. This allows the caller to extract information out of the bare
|
||||
// inner list.
|
||||
//
|
||||
// This function will return once it encounters the end of the bare inner list,
|
||||
// or something that is not a bare inner list. If it cannot consume the entire
|
||||
// given string, the ok value returned will be false.
|
||||
func ParseBareInnerList(s string, f func(bareItem, param string)) (ok bool) {
|
||||
_, rest, ok := consumeBareInnerList(s, f)
|
||||
return rest == "" && ok
|
||||
}
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-item.
|
||||
func consumeItem(s string, f func(bareItem, param string)) (consumed, rest string, ok bool) {
|
||||
var bareItem, param string
|
||||
if bareItem, rest, ok = consumeBareItem(s); !ok {
|
||||
return "", s, ok
|
||||
}
|
||||
if param, rest, ok = consumeParameter(rest, nil); !ok {
|
||||
return "", s, ok
|
||||
}
|
||||
if f != nil {
|
||||
f(bareItem, param)
|
||||
}
|
||||
return s[:len(s)-len(rest)], rest, true
|
||||
}
|
||||
|
||||
// ParseItem parses an item from a given HTTP Structured Field Values.
|
||||
//
|
||||
// Given an HTTP SFV string that represents an item, it will call the given
|
||||
// function once, with the bare item and the parameter of the item. This allows
|
||||
// the caller to extract information out of the item.
|
||||
//
|
||||
// This function will return once it encounters the end of the string, or
|
||||
// something that is not an item. If it cannot consume the entire given
|
||||
// string, the ok value returned will be false.
|
||||
//
|
||||
// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-item.
|
||||
func ParseItem(s string, f func(bareItem, param string)) (ok bool) {
|
||||
_, rest, ok := consumeItem(s, f)
|
||||
return rest == "" && ok
|
||||
}
|
||||
|
||||
// ParseDictionary parses a dictionary from a given HTTP Structured Field
|
||||
// Values.
|
||||
//
|
||||
// Given an HTTP SFV string that represents a dictionary, it will call the
|
||||
// given function using each of the keys, values, and parameters contained in
|
||||
// the dictionary. This allows the caller to extract information out of the
|
||||
// dictionary.
|
||||
//
|
||||
// This function will return once it encounters the end of the string, or
|
||||
// something that is not a dictionary. If it cannot consume the entire given
|
||||
// string, the ok value returned will be false.
|
||||
//
|
||||
// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-dictionary.
|
||||
func ParseDictionary(s string, f func(key, val, param string)) (ok bool) {
|
||||
for len(s) != 0 {
|
||||
var key, val, param string
|
||||
val = "?1" // Default value for empty val is boolean true.
|
||||
if key, s, ok = consumeKey(s); !ok {
|
||||
return ok
|
||||
}
|
||||
if len(s) != 0 && s[0] == '=' {
|
||||
s = s[1:]
|
||||
if len(s) != 0 && s[0] == '(' {
|
||||
if val, s, ok = consumeBareInnerList(s, nil); !ok {
|
||||
return ok
|
||||
}
|
||||
} else {
|
||||
if val, s, ok = consumeBareItem(s); !ok {
|
||||
return ok
|
||||
}
|
||||
}
|
||||
}
|
||||
if param, s, ok = consumeParameter(s, nil); !ok {
|
||||
return ok
|
||||
}
|
||||
if f != nil {
|
||||
f(key, val, param)
|
||||
}
|
||||
s = s[countLeftWhitespace(s):]
|
||||
if len(s) == 0 {
|
||||
break
|
||||
}
|
||||
if s[0] == ',' {
|
||||
s = s[1:]
|
||||
}
|
||||
s = s[countLeftWhitespace(s):]
|
||||
if len(s) == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc9651.html#parse-param.
|
||||
func consumeParameter(s string, f func(key, val string)) (consumed, rest string, ok bool) {
|
||||
rest = s
|
||||
for len(rest) != 0 {
|
||||
var key, val string
|
||||
val = "?1" // Default value for empty val is boolean true.
|
||||
if rest[0] != ';' {
|
||||
break
|
||||
}
|
||||
rest = rest[1:]
|
||||
rest = rest[countLeftWhitespace(rest):]
|
||||
key, rest, ok = consumeKey(rest)
|
||||
if !ok {
|
||||
return "", s, ok
|
||||
}
|
||||
if len(rest) != 0 && rest[0] == '=' {
|
||||
rest = rest[1:]
|
||||
val, rest, ok = consumeBareItem(rest)
|
||||
if !ok {
|
||||
return "", s, ok
|
||||
}
|
||||
}
|
||||
if f != nil {
|
||||
f(key, val)
|
||||
}
|
||||
}
|
||||
return s[:len(s)-len(rest)], rest, true
|
||||
}
|
||||
|
||||
// ParseParameter parses a parameter from a given HTTP Structured Field Values.
|
||||
//
|
||||
// Given an HTTP SFV string that represents a parameter, it will call the given
|
||||
// function using each of the keys and values contained in the parameter. This
|
||||
// allows the caller to extract information out of the parameter.
|
||||
//
|
||||
// This function will return once it encounters the end of the string, or
|
||||
// something that is not a parameter. If it cannot consume the entire given
|
||||
// string, the ok value returned will be false.
|
||||
//
|
||||
// https://www.rfc-editor.org/rfc/rfc9651.html#parse-param.
|
||||
func ParseParameter(s string, f func(key, val string)) (ok bool) {
|
||||
_, rest, ok := consumeParameter(s, f)
|
||||
return rest == "" && ok
|
||||
}
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-key.
|
||||
func consumeKey(s string) (consumed, rest string, ok bool) {
|
||||
if len(s) == 0 || (!isLCAlpha(s[0]) && s[0] != '*') {
|
||||
return "", s, false
|
||||
}
|
||||
i := 0
|
||||
for _, ch := range []byte(s) {
|
||||
if !isLCAlpha(ch) && !isDigit(ch) && !slices.Contains([]byte("_-.*"), ch) {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
return s[:i], s[i:], true
|
||||
}
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-integer-or-decim.
|
||||
func consumeIntegerOrDecimal(s string) (consumed, rest string, ok bool) {
|
||||
var i, signOffset, periodIndex int
|
||||
var isDecimal bool
|
||||
if i < len(s) && s[i] == '-' {
|
||||
i++
|
||||
signOffset++
|
||||
}
|
||||
if i >= len(s) {
|
||||
return "", s, false
|
||||
}
|
||||
if !isDigit(s[i]) {
|
||||
return "", s, false
|
||||
}
|
||||
for i < len(s) {
|
||||
ch := s[i]
|
||||
if isDigit(ch) {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if !isDecimal && ch == '.' {
|
||||
if i-signOffset > 12 {
|
||||
return "", s, false
|
||||
}
|
||||
periodIndex = i
|
||||
isDecimal = true
|
||||
i++
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if !isDecimal && i-signOffset > 15 {
|
||||
return "", s, false
|
||||
}
|
||||
if isDecimal {
|
||||
if i-signOffset > 16 {
|
||||
return "", s, false
|
||||
}
|
||||
if s[i-1] == '.' {
|
||||
return "", s, false
|
||||
}
|
||||
if i-periodIndex-1 > 3 {
|
||||
return "", s, false
|
||||
}
|
||||
}
|
||||
return s[:i], s[i:], true
|
||||
}
|
||||
|
||||
// ParseInteger parses an integer from a given HTTP Structured Field Values.
|
||||
//
|
||||
// The entire HTTP SFV string must consist of a valid integer. It returns the
|
||||
// parsed integer and an ok boolean value, indicating success or not.
|
||||
//
|
||||
// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-integer-or-decim.
|
||||
func ParseInteger(s string) (parsed int64, ok bool) {
|
||||
if _, rest, ok := consumeIntegerOrDecimal(s); !ok || rest != "" {
|
||||
return 0, false
|
||||
}
|
||||
if n, err := strconv.ParseInt(s, 10, 64); err == nil {
|
||||
return n, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// ParseDecimal parses a decimal from a given HTTP Structured Field Values.
|
||||
//
|
||||
// The entire HTTP SFV string must consist of a valid decimal. It returns the
|
||||
// parsed decimal and an ok boolean value, indicating success or not.
|
||||
//
|
||||
// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-integer-or-decim.
|
||||
func ParseDecimal(s string) (parsed float64, ok bool) {
|
||||
if _, rest, ok := consumeIntegerOrDecimal(s); !ok || rest != "" {
|
||||
return 0, false
|
||||
}
|
||||
if !strings.Contains(s, ".") {
|
||||
return 0, false
|
||||
}
|
||||
if n, err := strconv.ParseFloat(s, 64); err == nil {
|
||||
return n, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-string.
|
||||
func consumeString(s string) (consumed, rest string, ok bool) {
|
||||
if len(s) == 0 || s[0] != '"' {
|
||||
return "", s, false
|
||||
}
|
||||
for i := 1; i < len(s); i++ {
|
||||
switch ch := s[i]; ch {
|
||||
case '\\':
|
||||
if i+1 >= len(s) {
|
||||
return "", s, false
|
||||
}
|
||||
i++
|
||||
if ch = s[i]; ch != '"' && ch != '\\' {
|
||||
return "", s, false
|
||||
}
|
||||
case '"':
|
||||
return s[:i+1], s[i+1:], true
|
||||
default:
|
||||
if !isVChar(ch) && !isSP(ch) {
|
||||
return "", s, false
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", s, false
|
||||
}
|
||||
|
||||
// ParseString parses a Go string from a given HTTP Structured Field Values.
|
||||
//
|
||||
// The entire HTTP SFV string must consist of a valid string. It returns the
|
||||
// parsed string and an ok boolean value, indicating success or not.
|
||||
//
|
||||
// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-string.
|
||||
func ParseString(s string) (parsed string, ok bool) {
|
||||
if _, rest, ok := consumeString(s); !ok || rest != "" {
|
||||
return "", false
|
||||
}
|
||||
return s[1 : len(s)-1], true
|
||||
}
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-token
|
||||
func consumeToken(s string) (consumed, rest string, ok bool) {
|
||||
if len(s) == 0 || (!isAlpha(s[0]) && s[0] != '*') {
|
||||
return "", s, false
|
||||
}
|
||||
i := 0
|
||||
for _, ch := range []byte(s) {
|
||||
if !isTChar(ch) && !slices.Contains([]byte(":/"), ch) {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
return s[:i], s[i:], true
|
||||
}
|
||||
|
||||
// ParseToken parses a token from a given HTTP Structured Field Values.
|
||||
//
|
||||
// The entire HTTP SFV string must consist of a valid token. It returns the
|
||||
// parsed token and an ok boolean value, indicating success or not.
|
||||
//
|
||||
// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-token
|
||||
func ParseToken(s string) (parsed string, ok bool) {
|
||||
if _, rest, ok := consumeToken(s); !ok || rest != "" {
|
||||
return "", false
|
||||
}
|
||||
return s, true
|
||||
}
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-byte-sequence.
|
||||
func consumeByteSequence(s string) (consumed, rest string, ok bool) {
|
||||
if len(s) == 0 || s[0] != ':' {
|
||||
return "", s, false
|
||||
}
|
||||
for i := 1; i < len(s); i++ {
|
||||
if ch := s[i]; ch == ':' {
|
||||
return s[:i+1], s[i+1:], true
|
||||
}
|
||||
if ch := s[i]; !isAlpha(ch) && !isDigit(ch) && !slices.Contains([]byte("+/="), ch) {
|
||||
return "", s, false
|
||||
}
|
||||
}
|
||||
return "", s, false
|
||||
}
|
||||
|
||||
// ParseByteSequence parses a byte sequence from a given HTTP Structured Field
|
||||
// Values.
|
||||
//
|
||||
// The entire HTTP SFV string must consist of a valid byte sequence. It returns
|
||||
// the parsed byte sequence and an ok boolean value, indicating success or not.
|
||||
//
|
||||
// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-byte-sequence.
|
||||
func ParseByteSequence(s string) (parsed []byte, ok bool) {
|
||||
if _, rest, ok := consumeByteSequence(s); !ok || rest != "" {
|
||||
return nil, false
|
||||
}
|
||||
return []byte(s[1 : len(s)-1]), true
|
||||
}
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-boolean.
|
||||
func consumeBoolean(s string) (consumed, rest string, ok bool) {
|
||||
if len(s) >= 2 && (s[:2] == "?0" || s[:2] == "?1") {
|
||||
return s[:2], s[2:], true
|
||||
}
|
||||
return "", s, false
|
||||
}
|
||||
|
||||
// ParseBoolean parses a boolean from a given HTTP Structured Field Values.
|
||||
//
|
||||
// The entire HTTP SFV string must consist of a valid boolean. It returns the
|
||||
// parsed boolean and an ok boolean value, indicating success or not.
|
||||
//
|
||||
// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-boolean.
|
||||
func ParseBoolean(s string) (parsed bool, ok bool) {
|
||||
if _, rest, ok := consumeBoolean(s); !ok || rest != "" {
|
||||
return false, false
|
||||
}
|
||||
return s == "?1", true
|
||||
}
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-date.
|
||||
func consumeDate(s string) (consumed, rest string, ok bool) {
|
||||
if len(s) == 0 || s[0] != '@' {
|
||||
return "", s, false
|
||||
}
|
||||
if _, rest, ok = consumeIntegerOrDecimal(s[1:]); !ok {
|
||||
return "", s, ok
|
||||
}
|
||||
consumed = s[:len(s)-len(rest)]
|
||||
if slices.Contains([]byte(consumed), '.') {
|
||||
return "", s, false
|
||||
}
|
||||
return consumed, rest, ok
|
||||
}
|
||||
|
||||
// ParseDate parses a date from a given HTTP Structured Field Values.
|
||||
//
|
||||
// The entire HTTP SFV string must consist of a valid date. It returns the
|
||||
// parsed date and an ok boolean value, indicating success or not.
|
||||
//
|
||||
// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-date.
|
||||
func ParseDate(s string) (parsed time.Time, ok bool) {
|
||||
if _, rest, ok := consumeDate(s); !ok || rest != "" {
|
||||
return time.Time{}, false
|
||||
}
|
||||
if n, ok := ParseInteger(s[1:]); !ok {
|
||||
return time.Time{}, false
|
||||
} else {
|
||||
return time.Unix(n, 0), true
|
||||
}
|
||||
}
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-display-string.
|
||||
func consumeDisplayString(s string) (consumed, rest string, ok bool) {
|
||||
// To prevent excessive allocation, especially when input is large, we
|
||||
// maintain a buffer of 4 bytes to keep track of the last rune we
|
||||
// encounter. This way, we can validate that the display string conforms to
|
||||
// UTF-8 without actually building the whole string.
|
||||
var lastRune [4]byte
|
||||
var runeLen int
|
||||
isPartOfValidRune := func(ch byte) bool {
|
||||
lastRune[runeLen] = ch
|
||||
runeLen++
|
||||
if utf8.FullRune(lastRune[:runeLen]) {
|
||||
r, s := utf8.DecodeRune(lastRune[:runeLen])
|
||||
if r == utf8.RuneError {
|
||||
return false
|
||||
}
|
||||
copy(lastRune[:], lastRune[s:runeLen])
|
||||
runeLen -= s
|
||||
return true
|
||||
}
|
||||
return runeLen <= 4
|
||||
}
|
||||
|
||||
if len(s) <= 1 || s[:2] != `%"` {
|
||||
return "", s, false
|
||||
}
|
||||
i := 2
|
||||
for i < len(s) {
|
||||
ch := s[i]
|
||||
if !isVChar(ch) && !isSP(ch) {
|
||||
return "", s, false
|
||||
}
|
||||
switch ch {
|
||||
case '"':
|
||||
if runeLen > 0 {
|
||||
return "", s, false
|
||||
}
|
||||
return s[:i+1], s[i+1:], true
|
||||
case '%':
|
||||
if i+2 >= len(s) {
|
||||
return "", s, false
|
||||
}
|
||||
if ch, ok = decOctetHex(s[i+1], s[i+2]); !ok {
|
||||
return "", s, ok
|
||||
}
|
||||
if ok = isPartOfValidRune(ch); !ok {
|
||||
return "", s, ok
|
||||
}
|
||||
i += 3
|
||||
default:
|
||||
if ok = isPartOfValidRune(ch); !ok {
|
||||
return "", s, ok
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
return "", s, false
|
||||
}
|
||||
|
||||
// ParseDisplayString parses a display string from a given HTTP Structured
|
||||
// Field Values.
|
||||
//
|
||||
// The entire HTTP SFV string must consist of a valid display string. It
|
||||
// returns the parsed display string and an ok boolean value, indicating
|
||||
// success or not.
|
||||
//
|
||||
// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-display-string.
|
||||
func ParseDisplayString(s string) (parsed string, ok bool) {
|
||||
if _, rest, ok := consumeDisplayString(s); !ok || rest != "" {
|
||||
return "", false
|
||||
}
|
||||
// consumeDisplayString() already validates that we have a valid display
|
||||
// string. Therefore, we can just construct the display string, without
|
||||
// validating it again.
|
||||
s = s[2 : len(s)-1]
|
||||
var b strings.Builder
|
||||
for i := 0; i < len(s); {
|
||||
if s[i] == '%' {
|
||||
decoded, _ := decOctetHex(s[i+1], s[i+2])
|
||||
b.WriteByte(decoded)
|
||||
i += 3
|
||||
continue
|
||||
}
|
||||
b.WriteByte(s[i])
|
||||
i++
|
||||
}
|
||||
return b.String(), true
|
||||
}
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc9651.html#parse-bare-item.
|
||||
func consumeBareItem(s string) (consumed, rest string, ok bool) {
|
||||
if len(s) == 0 {
|
||||
return "", s, false
|
||||
}
|
||||
ch := s[0]
|
||||
switch {
|
||||
case ch == '-' || isDigit(ch):
|
||||
return consumeIntegerOrDecimal(s)
|
||||
case ch == '"':
|
||||
return consumeString(s)
|
||||
case ch == '*' || isAlpha(ch):
|
||||
return consumeToken(s)
|
||||
case ch == ':':
|
||||
return consumeByteSequence(s)
|
||||
case ch == '?':
|
||||
return consumeBoolean(s)
|
||||
case ch == '@':
|
||||
return consumeDate(s)
|
||||
case ch == '%':
|
||||
return consumeDisplayString(s)
|
||||
default:
|
||||
return "", s, false
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -2,6 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin && go1.12
|
||||
//go:build darwin
|
||||
|
||||
// This exists solely so we can linkname in symbols from syscall.
|
||||
|
||||
+5
-2
@@ -6,7 +6,10 @@
|
||||
|
||||
package socket
|
||||
|
||||
import "unsafe"
|
||||
import (
|
||||
"encoding/binary"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func (h *msghdr) pack(vs []iovec, bs [][]byte, oob []byte, sa []byte) {
|
||||
for i := range vs {
|
||||
@@ -31,5 +34,5 @@ func (h *msghdr) controllen() int {
|
||||
}
|
||||
|
||||
func (h *msghdr) flags() int {
|
||||
return int(NativeEndian.Uint32(h.Pad_cgo_2[:]))
|
||||
return int(binary.NativeEndian.Uint32(h.Pad_cgo_2[:]))
|
||||
}
|
||||
|
||||
+3
-2
@@ -7,6 +7,7 @@
|
||||
package socket // import "golang.org/x/net/internal/socket"
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"runtime"
|
||||
@@ -58,7 +59,7 @@ func (o *Option) GetInt(c *Conn) (int, error) {
|
||||
if o.Len == 1 {
|
||||
return int(b[0]), nil
|
||||
}
|
||||
return int(NativeEndian.Uint32(b[:4])), nil
|
||||
return int(binary.NativeEndian.Uint32(b[:4])), nil
|
||||
}
|
||||
|
||||
// Set writes the option and value to the kernel.
|
||||
@@ -84,7 +85,7 @@ func (o *Option) SetInt(c *Conn, v int) error {
|
||||
b = []byte{byte(v)}
|
||||
} else {
|
||||
var bb [4]byte
|
||||
NativeEndian.PutUint32(bb[:o.Len], uint32(v))
|
||||
binary.NativeEndian.PutUint32(bb[:o.Len], uint32(v))
|
||||
b = bb[:4]
|
||||
}
|
||||
return o.set(c, b)
|
||||
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package socket
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// NativeEndian is the machine native endian implementation of ByteOrder.
|
||||
var NativeEndian binary.ByteOrder
|
||||
|
||||
func init() {
|
||||
i := uint32(1)
|
||||
b := (*[4]byte)(unsafe.Pointer(&i))
|
||||
if b[0] == 1 {
|
||||
NativeEndian = binary.LittleEndian
|
||||
} else {
|
||||
NativeEndian = binary.BigEndian
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -36,7 +36,7 @@ func marshalSockaddr(ip net.IP, port int, zone string, b []byte) int {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
switch runtime.GOOS {
|
||||
case "android", "illumos", "linux", "solaris", "windows":
|
||||
NativeEndian.PutUint16(b[:2], uint16(sysAF_INET))
|
||||
binary.NativeEndian.PutUint16(b[:2], uint16(sysAF_INET))
|
||||
default:
|
||||
b[0] = sizeofSockaddrInet4
|
||||
b[1] = sysAF_INET
|
||||
@@ -48,7 +48,7 @@ func marshalSockaddr(ip net.IP, port int, zone string, b []byte) int {
|
||||
if ip6 := ip.To16(); ip6 != nil && ip.To4() == nil {
|
||||
switch runtime.GOOS {
|
||||
case "android", "illumos", "linux", "solaris", "windows":
|
||||
NativeEndian.PutUint16(b[:2], uint16(sysAF_INET6))
|
||||
binary.NativeEndian.PutUint16(b[:2], uint16(sysAF_INET6))
|
||||
default:
|
||||
b[0] = sizeofSockaddrInet6
|
||||
b[1] = sysAF_INET6
|
||||
@@ -56,7 +56,7 @@ func marshalSockaddr(ip net.IP, port int, zone string, b []byte) int {
|
||||
binary.BigEndian.PutUint16(b[2:4], uint16(port))
|
||||
copy(b[8:24], ip6)
|
||||
if zone != "" {
|
||||
NativeEndian.PutUint32(b[24:28], uint32(zoneCache.index(zone)))
|
||||
binary.NativeEndian.PutUint32(b[24:28], uint32(zoneCache.index(zone)))
|
||||
}
|
||||
return sizeofSockaddrInet6
|
||||
}
|
||||
@@ -70,7 +70,7 @@ func parseInetAddr(b []byte, network string) (net.Addr, error) {
|
||||
var af int
|
||||
switch runtime.GOOS {
|
||||
case "android", "illumos", "linux", "solaris", "windows":
|
||||
af = int(NativeEndian.Uint16(b[:2]))
|
||||
af = int(binary.NativeEndian.Uint16(b[:2]))
|
||||
default:
|
||||
af = int(b[1])
|
||||
}
|
||||
@@ -89,7 +89,7 @@ func parseInetAddr(b []byte, network string) (net.Addr, error) {
|
||||
}
|
||||
ip = make(net.IP, net.IPv6len)
|
||||
copy(ip, b[8:24])
|
||||
if id := int(NativeEndian.Uint32(b[24:28])); id > 0 {
|
||||
if id := int(binary.NativeEndian.Uint32(b[24:28])); id > 0 {
|
||||
zone = zoneCache.name(id)
|
||||
}
|
||||
}
|
||||
|
||||
+8
-10
@@ -9,8 +9,6 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/net/internal/socket"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -68,12 +66,12 @@ func (h *Header) Marshal() ([]byte, error) {
|
||||
flagsAndFragOff := (h.FragOff & 0x1fff) | int(h.Flags<<13)
|
||||
switch runtime.GOOS {
|
||||
case "darwin", "ios", "dragonfly", "netbsd":
|
||||
socket.NativeEndian.PutUint16(b[2:4], uint16(h.TotalLen))
|
||||
socket.NativeEndian.PutUint16(b[6:8], uint16(flagsAndFragOff))
|
||||
binary.NativeEndian.PutUint16(b[2:4], uint16(h.TotalLen))
|
||||
binary.NativeEndian.PutUint16(b[6:8], uint16(flagsAndFragOff))
|
||||
case "freebsd":
|
||||
if freebsdVersion < 1100000 {
|
||||
socket.NativeEndian.PutUint16(b[2:4], uint16(h.TotalLen))
|
||||
socket.NativeEndian.PutUint16(b[6:8], uint16(flagsAndFragOff))
|
||||
binary.NativeEndian.PutUint16(b[2:4], uint16(h.TotalLen))
|
||||
binary.NativeEndian.PutUint16(b[6:8], uint16(flagsAndFragOff))
|
||||
} else {
|
||||
binary.BigEndian.PutUint16(b[2:4], uint16(h.TotalLen))
|
||||
binary.BigEndian.PutUint16(b[6:8], uint16(flagsAndFragOff))
|
||||
@@ -127,15 +125,15 @@ func (h *Header) Parse(b []byte) error {
|
||||
h.Dst = net.IPv4(b[16], b[17], b[18], b[19])
|
||||
switch runtime.GOOS {
|
||||
case "darwin", "ios", "dragonfly", "netbsd":
|
||||
h.TotalLen = int(socket.NativeEndian.Uint16(b[2:4])) + hdrlen
|
||||
h.FragOff = int(socket.NativeEndian.Uint16(b[6:8]))
|
||||
h.TotalLen = int(binary.NativeEndian.Uint16(b[2:4])) + hdrlen
|
||||
h.FragOff = int(binary.NativeEndian.Uint16(b[6:8]))
|
||||
case "freebsd":
|
||||
if freebsdVersion < 1100000 {
|
||||
h.TotalLen = int(socket.NativeEndian.Uint16(b[2:4]))
|
||||
h.TotalLen = int(binary.NativeEndian.Uint16(b[2:4]))
|
||||
if freebsdVersion < 1000000 {
|
||||
h.TotalLen += hdrlen
|
||||
}
|
||||
h.FragOff = int(socket.NativeEndian.Uint16(b[6:8]))
|
||||
h.FragOff = int(binary.NativeEndian.Uint16(b[6:8]))
|
||||
} else {
|
||||
h.TotalLen = int(binary.BigEndian.Uint16(b[2:4]))
|
||||
h.FragOff = int(binary.BigEndian.Uint16(b[6:8]))
|
||||
|
||||
+2
-1
@@ -7,6 +7,7 @@
|
||||
package ipv6
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/net/internal/iana"
|
||||
@@ -19,7 +20,7 @@ func marshal2292HopLimit(b []byte, cm *ControlMessage) []byte {
|
||||
m := socket.ControlMessage(b)
|
||||
m.MarshalHeader(iana.ProtocolIPv6, unix.IPV6_2292HOPLIMIT, 4)
|
||||
if cm != nil {
|
||||
socket.NativeEndian.PutUint32(m.Data(4), uint32(cm.HopLimit))
|
||||
binary.NativeEndian.PutUint32(m.Data(4), uint32(cm.HopLimit))
|
||||
}
|
||||
return m.Next(4)
|
||||
}
|
||||
|
||||
+5
-4
@@ -7,6 +7,7 @@
|
||||
package ipv6
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"unsafe"
|
||||
|
||||
@@ -20,26 +21,26 @@ func marshalTrafficClass(b []byte, cm *ControlMessage) []byte {
|
||||
m := socket.ControlMessage(b)
|
||||
m.MarshalHeader(iana.ProtocolIPv6, unix.IPV6_TCLASS, 4)
|
||||
if cm != nil {
|
||||
socket.NativeEndian.PutUint32(m.Data(4), uint32(cm.TrafficClass))
|
||||
binary.NativeEndian.PutUint32(m.Data(4), uint32(cm.TrafficClass))
|
||||
}
|
||||
return m.Next(4)
|
||||
}
|
||||
|
||||
func parseTrafficClass(cm *ControlMessage, b []byte) {
|
||||
cm.TrafficClass = int(socket.NativeEndian.Uint32(b[:4]))
|
||||
cm.TrafficClass = int(binary.NativeEndian.Uint32(b[:4]))
|
||||
}
|
||||
|
||||
func marshalHopLimit(b []byte, cm *ControlMessage) []byte {
|
||||
m := socket.ControlMessage(b)
|
||||
m.MarshalHeader(iana.ProtocolIPv6, unix.IPV6_HOPLIMIT, 4)
|
||||
if cm != nil {
|
||||
socket.NativeEndian.PutUint32(m.Data(4), uint32(cm.HopLimit))
|
||||
binary.NativeEndian.PutUint32(m.Data(4), uint32(cm.HopLimit))
|
||||
}
|
||||
return m.Next(4)
|
||||
}
|
||||
|
||||
func parseHopLimit(cm *ControlMessage, b []byte) {
|
||||
cm.HopLimit = int(socket.NativeEndian.Uint32(b[:4]))
|
||||
cm.HopLimit = int(binary.NativeEndian.Uint32(b[:4]))
|
||||
}
|
||||
|
||||
func marshalPacketInfo(b []byte, cm *ControlMessage) []byte {
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+1
-1
File diff suppressed because one or more lines are too long
+5
@@ -51,6 +51,7 @@ package publicsuffix // import "golang.org/x/net/publicsuffix"
|
||||
import (
|
||||
"fmt"
|
||||
"net/http/cookiejar"
|
||||
"net/netip"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -84,6 +85,10 @@ func (list) String() string {
|
||||
// domains like "foo.appspot.com" can be found at
|
||||
// https://wiki.mozilla.org/Public_Suffix_List/Use_Cases
|
||||
func PublicSuffix(domain string) (publicSuffix string, icann bool) {
|
||||
if _, err := netip.ParseAddr(domain); err == nil {
|
||||
return domain, false
|
||||
}
|
||||
|
||||
lo, hi := uint32(0), uint32(numTLD)
|
||||
s, suffix, icannNode, wildcard := domain, len(domain), false, false
|
||||
loop:
|
||||
|
||||
+6
-6
@@ -4,7 +4,7 @@ package publicsuffix
|
||||
|
||||
import _ "embed"
|
||||
|
||||
const version = "publicsuffix.org's public_suffix_list.dat, git revision 2c960dac3d39ba521eb5db9da192968f5be0aded (2025-03-18T07:22:13Z)"
|
||||
const version = "publicsuffix.org's public_suffix_list.dat, git revision d6c92f1bbb7433e5db7b8405c25d4035fb8ff376 (2026-02-06T07:36:33Z)"
|
||||
|
||||
const (
|
||||
nodesBits = 40
|
||||
@@ -26,7 +26,7 @@ const (
|
||||
)
|
||||
|
||||
// numTLD is the number of top level domains.
|
||||
const numTLD = 1454
|
||||
const numTLD = 1450
|
||||
|
||||
// text is the combined text of all labels.
|
||||
//
|
||||
@@ -63,8 +63,8 @@ var nodes uint40String
|
||||
//go:embed data/children
|
||||
var children uint32String
|
||||
|
||||
// max children 870 (capacity 1023)
|
||||
// max text offset 31785 (capacity 65535)
|
||||
// max children 935 (capacity 1023)
|
||||
// max text offset 32332 (capacity 65535)
|
||||
// max text length 31 (capacity 63)
|
||||
// max hi 10100 (capacity 16383)
|
||||
// max lo 10095 (capacity 16383)
|
||||
// max hi 10533 (capacity 16383)
|
||||
// max lo 10528 (capacity 16383)
|
||||
|
||||
+2
-2
@@ -144,8 +144,8 @@ func (g *Group) SetLimit(n int) {
|
||||
g.sem = nil
|
||||
return
|
||||
}
|
||||
if len(g.sem) != 0 {
|
||||
panic(fmt.Errorf("errgroup: modify limit while %v goroutines in the group are still active", len(g.sem)))
|
||||
if active := len(g.sem); active != 0 {
|
||||
panic(fmt.Errorf("errgroup: modify limit while %v goroutines in the group are still active", active))
|
||||
}
|
||||
g.sem = make(chan token, n)
|
||||
}
|
||||
|
||||
+7
-7
@@ -22,7 +22,7 @@ var errGoexit = errors.New("runtime.Goexit was called")
|
||||
// A panicError is an arbitrary value recovered from a panic
|
||||
// with the stack trace during the execution of given function.
|
||||
type panicError struct {
|
||||
value interface{}
|
||||
value any
|
||||
stack []byte
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ func (p *panicError) Unwrap() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func newPanicError(v interface{}) error {
|
||||
func newPanicError(v any) error {
|
||||
stack := debug.Stack()
|
||||
|
||||
// The first line of the stack trace is of the form "goroutine N [status]:"
|
||||
@@ -58,7 +58,7 @@ type call struct {
|
||||
|
||||
// These fields are written once before the WaitGroup is done
|
||||
// and are only read after the WaitGroup is done.
|
||||
val interface{}
|
||||
val any
|
||||
err error
|
||||
|
||||
// These fields are read and written with the singleflight
|
||||
@@ -78,7 +78,7 @@ type Group struct {
|
||||
// Result holds the results of Do, so they can be passed
|
||||
// on a channel.
|
||||
type Result struct {
|
||||
Val interface{}
|
||||
Val any
|
||||
Err error
|
||||
Shared bool
|
||||
}
|
||||
@@ -88,7 +88,7 @@ type Result struct {
|
||||
// time. If a duplicate comes in, the duplicate caller waits for the
|
||||
// original to complete and receives the same results.
|
||||
// The return value shared indicates whether v was given to multiple callers.
|
||||
func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
|
||||
func (g *Group) Do(key string, fn func() (any, error)) (v any, err error, shared bool) {
|
||||
g.mu.Lock()
|
||||
if g.m == nil {
|
||||
g.m = make(map[string]*call)
|
||||
@@ -118,7 +118,7 @@ func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, e
|
||||
// results when they are ready.
|
||||
//
|
||||
// The returned channel will not be closed.
|
||||
func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result {
|
||||
func (g *Group) DoChan(key string, fn func() (any, error)) <-chan Result {
|
||||
ch := make(chan Result, 1)
|
||||
g.mu.Lock()
|
||||
if g.m == nil {
|
||||
@@ -141,7 +141,7 @@ func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result
|
||||
}
|
||||
|
||||
// doCall handles the single call for a key.
|
||||
func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) {
|
||||
func (g *Group) doCall(c *call, key string, fn func() (any, error)) {
|
||||
normalReturn := false
|
||||
recovered := false
|
||||
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin && arm64 && gc
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
TEXT libc_sysctlbyname_trampoline<>(SB),NOSPLIT,$0-0
|
||||
JMP libc_sysctlbyname(SB)
|
||||
GLOBL ·libc_sysctlbyname_trampoline_addr(SB), RODATA, $8
|
||||
DATA ·libc_sysctlbyname_trampoline_addr(SB)/8, $libc_sysctlbyname_trampoline<>(SB)
|
||||
+3
-6
@@ -44,14 +44,11 @@ func initOptions() {
|
||||
}
|
||||
|
||||
func archInit() {
|
||||
switch runtime.GOOS {
|
||||
case "freebsd":
|
||||
if runtime.GOOS == "freebsd" {
|
||||
readARM64Registers()
|
||||
case "linux", "netbsd", "openbsd":
|
||||
} else {
|
||||
// Most platforms don't seem to allow directly reading these registers.
|
||||
doinit()
|
||||
default:
|
||||
// Many platforms don't seem to allow reading these registers.
|
||||
setMinimalFeatures()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-8
@@ -9,31 +9,27 @@
|
||||
// func getisar0() uint64
|
||||
TEXT ·getisar0(SB),NOSPLIT,$0-8
|
||||
// get Instruction Set Attributes 0 into x0
|
||||
// mrs x0, ID_AA64ISAR0_EL1 = d5380600
|
||||
WORD $0xd5380600
|
||||
MRS ID_AA64ISAR0_EL1, R0
|
||||
MOVD R0, ret+0(FP)
|
||||
RET
|
||||
|
||||
// func getisar1() uint64
|
||||
TEXT ·getisar1(SB),NOSPLIT,$0-8
|
||||
// get Instruction Set Attributes 1 into x0
|
||||
// mrs x0, ID_AA64ISAR1_EL1 = d5380620
|
||||
WORD $0xd5380620
|
||||
MRS ID_AA64ISAR1_EL1, R0
|
||||
MOVD R0, ret+0(FP)
|
||||
RET
|
||||
|
||||
// func getpfr0() uint64
|
||||
TEXT ·getpfr0(SB),NOSPLIT,$0-8
|
||||
// get Processor Feature Register 0 into x0
|
||||
// mrs x0, ID_AA64PFR0_EL1 = d5380400
|
||||
WORD $0xd5380400
|
||||
MRS ID_AA64PFR0_EL1, R0
|
||||
MOVD R0, ret+0(FP)
|
||||
RET
|
||||
|
||||
// func getzfr0() uint64
|
||||
TEXT ·getzfr0(SB),NOSPLIT,$0-8
|
||||
// get SVE Feature Register 0 into x0
|
||||
// mrs x0, ID_AA64ZFR0_EL1 = d5380480
|
||||
WORD $0xd5380480
|
||||
MRS ID_AA64ZFR0_EL1, R0
|
||||
MOVD R0, ret+0(FP)
|
||||
RET
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin && arm64 && gc
|
||||
|
||||
package cpu
|
||||
|
||||
func doinit() {
|
||||
setMinimalFeatures()
|
||||
|
||||
// The feature flags are explained in [Instruction Set Detection].
|
||||
// There are some differences between MacOS versions:
|
||||
//
|
||||
// MacOS 11 and 12 do not have "hw.optional" sysctl values for some of the features.
|
||||
//
|
||||
// MacOS 13 changed some of the naming conventions to align with ARM Architecture Reference Manual.
|
||||
// For example "hw.optional.armv8_2_sha512" became "hw.optional.arm.FEAT_SHA512".
|
||||
// It currently checks both to stay compatible with MacOS 11 and 12.
|
||||
// The old names also work with MacOS 13, however it's not clear whether
|
||||
// they will continue working with future OS releases.
|
||||
//
|
||||
// Once MacOS 12 is no longer supported the old names can be removed.
|
||||
//
|
||||
// [Instruction Set Detection]: https://developer.apple.com/documentation/kernel/1387446-sysctlbyname/determining_instruction_set_characteristics
|
||||
|
||||
// Encryption, hashing and checksum capabilities
|
||||
|
||||
// For the following flags there are no MacOS 11 sysctl flags.
|
||||
ARM64.HasAES = true || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_AES\x00"))
|
||||
ARM64.HasPMULL = true || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_PMULL\x00"))
|
||||
ARM64.HasSHA1 = true || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SHA1\x00"))
|
||||
ARM64.HasSHA2 = true || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SHA256\x00"))
|
||||
|
||||
ARM64.HasSHA3 = darwinSysctlEnabled([]byte("hw.optional.armv8_2_sha3\x00")) || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SHA3\x00"))
|
||||
ARM64.HasSHA512 = darwinSysctlEnabled([]byte("hw.optional.armv8_2_sha512\x00")) || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SHA512\x00"))
|
||||
|
||||
ARM64.HasCRC32 = darwinSysctlEnabled([]byte("hw.optional.armv8_crc32\x00"))
|
||||
|
||||
// Atomic and memory ordering
|
||||
ARM64.HasATOMICS = darwinSysctlEnabled([]byte("hw.optional.armv8_1_atomics\x00")) || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_LSE\x00"))
|
||||
ARM64.HasLRCPC = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_LRCPC\x00"))
|
||||
|
||||
// SIMD and floating point capabilities
|
||||
ARM64.HasFPHP = darwinSysctlEnabled([]byte("hw.optional.neon_fp16\x00")) || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_FP16\x00"))
|
||||
ARM64.HasASIMDHP = darwinSysctlEnabled([]byte("hw.optional.neon_hpfp\x00")) || darwinSysctlEnabled([]byte("hw.optional.AdvSIMD_HPFPCvt\x00"))
|
||||
ARM64.HasASIMDRDM = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_RDM\x00"))
|
||||
ARM64.HasASIMDDP = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_DotProd\x00"))
|
||||
ARM64.HasASIMDFHM = darwinSysctlEnabled([]byte("hw.optional.armv8_2_fhm\x00")) || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_FHM\x00"))
|
||||
ARM64.HasI8MM = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_I8MM\x00"))
|
||||
|
||||
ARM64.HasJSCVT = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_JSCVT\x00"))
|
||||
ARM64.HasFCMA = darwinSysctlEnabled([]byte("hw.optional.armv8_3_compnum\x00")) || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_FCMA\x00"))
|
||||
|
||||
// Miscellaneous
|
||||
ARM64.HasDCPOP = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_DPB\x00"))
|
||||
ARM64.HasEVTSTRM = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_ECV\x00"))
|
||||
ARM64.HasDIT = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_DIT\x00"))
|
||||
|
||||
// Not supported, but added for completeness
|
||||
ARM64.HasCPUID = false
|
||||
|
||||
ARM64.HasSM3 = false // darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SM3\x00"))
|
||||
ARM64.HasSM4 = false // darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SM4\x00"))
|
||||
ARM64.HasSVE = false // darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SVE\x00"))
|
||||
ARM64.HasSVE2 = false // darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SVE2\x00"))
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin && arm64 && !gc
|
||||
|
||||
package cpu
|
||||
|
||||
func doinit() {
|
||||
setMinimalFeatures()
|
||||
|
||||
ARM64.HasASIMD = true
|
||||
ARM64.HasFP = true
|
||||
|
||||
// Go already assumes these to be available because they were on the M1
|
||||
// and these are supported on all Apple arm64 chips.
|
||||
ARM64.HasAES = true
|
||||
ARM64.HasPMULL = true
|
||||
ARM64.HasSHA1 = true
|
||||
ARM64.HasSHA2 = true
|
||||
|
||||
if runtime.GOOS != "ios" {
|
||||
// Apple A7 processors do not support these, however
|
||||
// M-series SoCs are at least armv8.4-a
|
||||
ARM64.HasCRC32 = true // armv8.1
|
||||
ARM64.HasATOMICS = true // armv8.2
|
||||
ARM64.HasJSCVT = true // armv8.3, if HasFP
|
||||
}
|
||||
}
|
||||
+1
@@ -9,3 +9,4 @@ package cpu
|
||||
func getisar0() uint64 { return 0 }
|
||||
func getisar1() uint64 { return 0 }
|
||||
func getpfr0() uint64 { return 0 }
|
||||
func getzfr0() uint64 { return 0 }
|
||||
|
||||
+4
-2
@@ -2,8 +2,10 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !linux && !netbsd && !openbsd && arm64
|
||||
//go:build !darwin && !linux && !netbsd && !openbsd && !windows && arm64
|
||||
|
||||
package cpu
|
||||
|
||||
func doinit() {}
|
||||
func doinit() {
|
||||
setMinimalFeatures()
|
||||
}
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func doinit() {
|
||||
// set HasASIMD and HasFP to true as per
|
||||
// https://learn.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions?view=msvc-170#base-requirements
|
||||
//
|
||||
// The ARM64 version of Windows always presupposes that it's running on an ARMv8 or later architecture.
|
||||
// Both floating-point and NEON support are presumed to be present in hardware.
|
||||
//
|
||||
ARM64.HasASIMD = true
|
||||
ARM64.HasFP = true
|
||||
|
||||
if windows.IsProcessorFeaturePresent(windows.PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE) {
|
||||
ARM64.HasAES = true
|
||||
ARM64.HasPMULL = true
|
||||
ARM64.HasSHA1 = true
|
||||
ARM64.HasSHA2 = true
|
||||
}
|
||||
ARM64.HasSHA3 = windows.IsProcessorFeaturePresent(windows.PF_ARM_SHA3_INSTRUCTIONS_AVAILABLE)
|
||||
ARM64.HasCRC32 = windows.IsProcessorFeaturePresent(windows.PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE)
|
||||
ARM64.HasSHA512 = windows.IsProcessorFeaturePresent(windows.PF_ARM_SHA512_INSTRUCTIONS_AVAILABLE)
|
||||
ARM64.HasATOMICS = windows.IsProcessorFeaturePresent(windows.PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE)
|
||||
if windows.IsProcessorFeaturePresent(windows.PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE) {
|
||||
ARM64.HasASIMDDP = true
|
||||
ARM64.HasASIMDRDM = true
|
||||
}
|
||||
if windows.IsProcessorFeaturePresent(windows.PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE) {
|
||||
ARM64.HasLRCPC = true
|
||||
ARM64.HasSM3 = true
|
||||
}
|
||||
ARM64.HasSVE = windows.IsProcessorFeaturePresent(windows.PF_ARM_SVE_INSTRUCTIONS_AVAILABLE)
|
||||
ARM64.HasSVE2 = windows.IsProcessorFeaturePresent(windows.PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE)
|
||||
ARM64.HasJSCVT = windows.IsProcessorFeaturePresent(windows.PF_ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE)
|
||||
}
|
||||
+122
-48
@@ -64,6 +64,80 @@ func initOptions() {
|
||||
|
||||
func archInit() {
|
||||
|
||||
// From internal/cpu
|
||||
const (
|
||||
// eax bits
|
||||
cpuid_AVXVNNI = 1 << 4
|
||||
|
||||
// ecx bits
|
||||
cpuid_SSE3 = 1 << 0
|
||||
cpuid_PCLMULQDQ = 1 << 1
|
||||
cpuid_AVX512VBMI = 1 << 1
|
||||
cpuid_AVX512VBMI2 = 1 << 6
|
||||
cpuid_SSSE3 = 1 << 9
|
||||
cpuid_AVX512GFNI = 1 << 8
|
||||
cpuid_AVX512VAES = 1 << 9
|
||||
cpuid_AVX512VNNI = 1 << 11
|
||||
cpuid_AVX512BITALG = 1 << 12
|
||||
cpuid_FMA = 1 << 12
|
||||
cpuid_AVX512VPOPCNTDQ = 1 << 14
|
||||
cpuid_SSE41 = 1 << 19
|
||||
cpuid_SSE42 = 1 << 20
|
||||
cpuid_POPCNT = 1 << 23
|
||||
cpuid_AES = 1 << 25
|
||||
cpuid_OSXSAVE = 1 << 27
|
||||
cpuid_AVX = 1 << 28
|
||||
|
||||
// "Extended Feature Flag" bits returned in EBX for CPUID EAX=0x7 ECX=0x0
|
||||
cpuid_BMI1 = 1 << 3
|
||||
cpuid_AVX2 = 1 << 5
|
||||
cpuid_BMI2 = 1 << 8
|
||||
cpuid_ERMS = 1 << 9
|
||||
cpuid_AVX512F = 1 << 16
|
||||
cpuid_AVX512DQ = 1 << 17
|
||||
cpuid_ADX = 1 << 19
|
||||
cpuid_AVX512CD = 1 << 28
|
||||
cpuid_SHA = 1 << 29
|
||||
cpuid_AVX512BW = 1 << 30
|
||||
cpuid_AVX512VL = 1 << 31
|
||||
|
||||
// "Extended Feature Flag" bits returned in ECX for CPUID EAX=0x7 ECX=0x0
|
||||
cpuid_AVX512_VBMI = 1 << 1
|
||||
cpuid_AVX512_VBMI2 = 1 << 6
|
||||
cpuid_GFNI = 1 << 8
|
||||
cpuid_AVX512VPCLMULQDQ = 1 << 10
|
||||
cpuid_AVX512_BITALG = 1 << 12
|
||||
|
||||
// edx bits
|
||||
cpuid_FSRM = 1 << 4
|
||||
// edx bits for CPUID 0x80000001
|
||||
cpuid_RDTSCP = 1 << 27
|
||||
)
|
||||
// Additional constants not in internal/cpu
|
||||
const (
|
||||
// eax=1: edx
|
||||
cpuid_SSE2 = 1 << 26
|
||||
// eax=1: ecx
|
||||
cpuid_CX16 = 1 << 13
|
||||
cpuid_RDRAND = 1 << 30
|
||||
// eax=7,ecx=0: ebx
|
||||
cpuid_RDSEED = 1 << 18
|
||||
cpuid_AVX512IFMA = 1 << 21
|
||||
cpuid_AVX512PF = 1 << 26
|
||||
cpuid_AVX512ER = 1 << 27
|
||||
// eax=7,ecx=0: edx
|
||||
cpuid_AVX5124VNNIW = 1 << 2
|
||||
cpuid_AVX5124FMAPS = 1 << 3
|
||||
cpuid_AMXBF16 = 1 << 22
|
||||
cpuid_AMXTile = 1 << 24
|
||||
cpuid_AMXInt8 = 1 << 25
|
||||
// eax=7,ecx=1: eax
|
||||
cpuid_AVX512BF16 = 1 << 5
|
||||
cpuid_AVXIFMA = 1 << 23
|
||||
// eax=7,ecx=1: edx
|
||||
cpuid_AVXVNNIInt8 = 1 << 4
|
||||
)
|
||||
|
||||
Initialized = true
|
||||
|
||||
maxID, _, _, _ := cpuid(0, 0)
|
||||
@@ -73,90 +147,90 @@ func archInit() {
|
||||
}
|
||||
|
||||
_, _, ecx1, edx1 := cpuid(1, 0)
|
||||
X86.HasSSE2 = isSet(26, edx1)
|
||||
X86.HasSSE2 = isSet(edx1, cpuid_SSE2)
|
||||
|
||||
X86.HasSSE3 = isSet(0, ecx1)
|
||||
X86.HasPCLMULQDQ = isSet(1, ecx1)
|
||||
X86.HasSSSE3 = isSet(9, ecx1)
|
||||
X86.HasFMA = isSet(12, ecx1)
|
||||
X86.HasCX16 = isSet(13, ecx1)
|
||||
X86.HasSSE41 = isSet(19, ecx1)
|
||||
X86.HasSSE42 = isSet(20, ecx1)
|
||||
X86.HasPOPCNT = isSet(23, ecx1)
|
||||
X86.HasAES = isSet(25, ecx1)
|
||||
X86.HasOSXSAVE = isSet(27, ecx1)
|
||||
X86.HasRDRAND = isSet(30, ecx1)
|
||||
X86.HasSSE3 = isSet(ecx1, cpuid_SSE3)
|
||||
X86.HasPCLMULQDQ = isSet(ecx1, cpuid_PCLMULQDQ)
|
||||
X86.HasSSSE3 = isSet(ecx1, cpuid_SSSE3)
|
||||
X86.HasFMA = isSet(ecx1, cpuid_FMA)
|
||||
X86.HasCX16 = isSet(ecx1, cpuid_CX16)
|
||||
X86.HasSSE41 = isSet(ecx1, cpuid_SSE41)
|
||||
X86.HasSSE42 = isSet(ecx1, cpuid_SSE42)
|
||||
X86.HasPOPCNT = isSet(ecx1, cpuid_POPCNT)
|
||||
X86.HasAES = isSet(ecx1, cpuid_AES)
|
||||
X86.HasOSXSAVE = isSet(ecx1, cpuid_OSXSAVE)
|
||||
X86.HasRDRAND = isSet(ecx1, cpuid_RDRAND)
|
||||
|
||||
var osSupportsAVX, osSupportsAVX512 bool
|
||||
// For XGETBV, OSXSAVE bit is required and sufficient.
|
||||
if X86.HasOSXSAVE {
|
||||
eax, _ := xgetbv()
|
||||
// Check if XMM and YMM registers have OS support.
|
||||
osSupportsAVX = isSet(1, eax) && isSet(2, eax)
|
||||
osSupportsAVX = isSet(eax, 1<<1) && isSet(eax, 1<<2)
|
||||
|
||||
if runtime.GOOS == "darwin" {
|
||||
// Darwin requires special AVX512 checks, see cpu_darwin_x86.go
|
||||
osSupportsAVX512 = osSupportsAVX && darwinSupportsAVX512()
|
||||
} else {
|
||||
// Check if OPMASK and ZMM registers have OS support.
|
||||
osSupportsAVX512 = osSupportsAVX && isSet(5, eax) && isSet(6, eax) && isSet(7, eax)
|
||||
osSupportsAVX512 = osSupportsAVX && isSet(eax, 1<<5) && isSet(eax, 1<<6) && isSet(eax, 1<<7)
|
||||
}
|
||||
}
|
||||
|
||||
X86.HasAVX = isSet(28, ecx1) && osSupportsAVX
|
||||
X86.HasAVX = isSet(ecx1, cpuid_AVX) && osSupportsAVX
|
||||
|
||||
if maxID < 7 {
|
||||
return
|
||||
}
|
||||
|
||||
eax7, ebx7, ecx7, edx7 := cpuid(7, 0)
|
||||
X86.HasBMI1 = isSet(3, ebx7)
|
||||
X86.HasAVX2 = isSet(5, ebx7) && osSupportsAVX
|
||||
X86.HasBMI2 = isSet(8, ebx7)
|
||||
X86.HasERMS = isSet(9, ebx7)
|
||||
X86.HasRDSEED = isSet(18, ebx7)
|
||||
X86.HasADX = isSet(19, ebx7)
|
||||
X86.HasBMI1 = isSet(ebx7, cpuid_BMI1)
|
||||
X86.HasAVX2 = isSet(ebx7, cpuid_AVX2) && osSupportsAVX
|
||||
X86.HasBMI2 = isSet(ebx7, cpuid_BMI2)
|
||||
X86.HasERMS = isSet(ebx7, cpuid_ERMS)
|
||||
X86.HasRDSEED = isSet(ebx7, cpuid_RDSEED)
|
||||
X86.HasADX = isSet(ebx7, cpuid_ADX)
|
||||
|
||||
X86.HasAVX512 = isSet(16, ebx7) && osSupportsAVX512 // Because avx-512 foundation is the core required extension
|
||||
X86.HasAVX512 = isSet(ebx7, cpuid_AVX512F) && osSupportsAVX512 // Because avx-512 foundation is the core required extension
|
||||
if X86.HasAVX512 {
|
||||
X86.HasAVX512F = true
|
||||
X86.HasAVX512CD = isSet(28, ebx7)
|
||||
X86.HasAVX512ER = isSet(27, ebx7)
|
||||
X86.HasAVX512PF = isSet(26, ebx7)
|
||||
X86.HasAVX512VL = isSet(31, ebx7)
|
||||
X86.HasAVX512BW = isSet(30, ebx7)
|
||||
X86.HasAVX512DQ = isSet(17, ebx7)
|
||||
X86.HasAVX512IFMA = isSet(21, ebx7)
|
||||
X86.HasAVX512VBMI = isSet(1, ecx7)
|
||||
X86.HasAVX5124VNNIW = isSet(2, edx7)
|
||||
X86.HasAVX5124FMAPS = isSet(3, edx7)
|
||||
X86.HasAVX512VPOPCNTDQ = isSet(14, ecx7)
|
||||
X86.HasAVX512VPCLMULQDQ = isSet(10, ecx7)
|
||||
X86.HasAVX512VNNI = isSet(11, ecx7)
|
||||
X86.HasAVX512GFNI = isSet(8, ecx7)
|
||||
X86.HasAVX512VAES = isSet(9, ecx7)
|
||||
X86.HasAVX512VBMI2 = isSet(6, ecx7)
|
||||
X86.HasAVX512BITALG = isSet(12, ecx7)
|
||||
X86.HasAVX512CD = isSet(ebx7, cpuid_AVX512CD)
|
||||
X86.HasAVX512ER = isSet(ebx7, cpuid_AVX512ER)
|
||||
X86.HasAVX512PF = isSet(ebx7, cpuid_AVX512PF)
|
||||
X86.HasAVX512VL = isSet(ebx7, cpuid_AVX512VL)
|
||||
X86.HasAVX512BW = isSet(ebx7, cpuid_AVX512BW)
|
||||
X86.HasAVX512DQ = isSet(ebx7, cpuid_AVX512DQ)
|
||||
X86.HasAVX512IFMA = isSet(ebx7, cpuid_AVX512IFMA)
|
||||
X86.HasAVX512VBMI = isSet(ecx7, cpuid_AVX512_VBMI)
|
||||
X86.HasAVX5124VNNIW = isSet(edx7, cpuid_AVX5124VNNIW)
|
||||
X86.HasAVX5124FMAPS = isSet(edx7, cpuid_AVX5124FMAPS)
|
||||
X86.HasAVX512VPOPCNTDQ = isSet(ecx7, cpuid_AVX512VPOPCNTDQ)
|
||||
X86.HasAVX512VPCLMULQDQ = isSet(ecx7, cpuid_AVX512VPCLMULQDQ)
|
||||
X86.HasAVX512VNNI = isSet(ecx7, cpuid_AVX512VNNI)
|
||||
X86.HasAVX512GFNI = isSet(ecx7, cpuid_AVX512GFNI)
|
||||
X86.HasAVX512VAES = isSet(ecx7, cpuid_AVX512VAES)
|
||||
X86.HasAVX512VBMI2 = isSet(ecx7, cpuid_AVX512VBMI2)
|
||||
X86.HasAVX512BITALG = isSet(ecx7, cpuid_AVX512BITALG)
|
||||
}
|
||||
|
||||
X86.HasAMXTile = isSet(24, edx7)
|
||||
X86.HasAMXInt8 = isSet(25, edx7)
|
||||
X86.HasAMXBF16 = isSet(22, edx7)
|
||||
X86.HasAMXTile = isSet(edx7, cpuid_AMXTile)
|
||||
X86.HasAMXInt8 = isSet(edx7, cpuid_AMXInt8)
|
||||
X86.HasAMXBF16 = isSet(edx7, cpuid_AMXBF16)
|
||||
|
||||
// These features depend on the second level of extended features.
|
||||
if eax7 >= 1 {
|
||||
eax71, _, _, edx71 := cpuid(7, 1)
|
||||
if X86.HasAVX512 {
|
||||
X86.HasAVX512BF16 = isSet(5, eax71)
|
||||
X86.HasAVX512BF16 = isSet(eax71, cpuid_AVX512BF16)
|
||||
}
|
||||
if X86.HasAVX {
|
||||
X86.HasAVXIFMA = isSet(23, eax71)
|
||||
X86.HasAVXVNNI = isSet(4, eax71)
|
||||
X86.HasAVXVNNIInt8 = isSet(4, edx71)
|
||||
X86.HasAVXIFMA = isSet(eax71, cpuid_AVXIFMA)
|
||||
X86.HasAVXVNNI = isSet(eax71, cpuid_AVXVNNI)
|
||||
X86.HasAVXVNNIInt8 = isSet(edx71, cpuid_AVXVNNIInt8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isSet(bitpos uint, value uint32) bool {
|
||||
return value&(1<<bitpos) != 0
|
||||
func isSet(hwc uint32, value uint32) bool {
|
||||
return hwc&value != 0
|
||||
}
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Minimal copy from internal/cpu and runtime to make sysctl calls.
|
||||
|
||||
//go:build darwin && arm64 && gc
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type Errno = syscall.Errno
|
||||
|
||||
// adapted from internal/cpu/cpu_arm64_darwin.go
|
||||
func darwinSysctlEnabled(name []byte) bool {
|
||||
out := int32(0)
|
||||
nout := unsafe.Sizeof(out)
|
||||
if ret := sysctlbyname(&name[0], (*byte)(unsafe.Pointer(&out)), &nout, nil, 0); ret != nil {
|
||||
return false
|
||||
}
|
||||
return out > 0
|
||||
}
|
||||
|
||||
//go:cgo_import_dynamic libc_sysctl sysctl "/usr/lib/libSystem.B.dylib"
|
||||
|
||||
var libc_sysctlbyname_trampoline_addr uintptr
|
||||
|
||||
// adapted from runtime/sys_darwin.go in the pattern of sysctl() above, as defined in x/sys/unix
|
||||
func sysctlbyname(name *byte, old *byte, oldlen *uintptr, new *byte, newlen uintptr) error {
|
||||
if _, _, err := syscall_syscall6(
|
||||
libc_sysctlbyname_trampoline_addr,
|
||||
uintptr(unsafe.Pointer(name)),
|
||||
uintptr(unsafe.Pointer(old)),
|
||||
uintptr(unsafe.Pointer(oldlen)),
|
||||
uintptr(unsafe.Pointer(new)),
|
||||
uintptr(newlen),
|
||||
0,
|
||||
); err != 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//go:cgo_import_dynamic libc_sysctlbyname sysctlbyname "/usr/lib/libSystem.B.dylib"
|
||||
|
||||
// Implemented in the runtime package (runtime/sys_darwin.go)
|
||||
func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)
|
||||
|
||||
//go:linkname syscall_syscall6 syscall.syscall6
|
||||
+8
-3
@@ -6,9 +6,7 @@
|
||||
|
||||
package unix
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
import "unsafe"
|
||||
|
||||
// ioctl itself should not be exposed directly, but additional get/set
|
||||
// functions for specific types are permissible.
|
||||
@@ -28,6 +26,13 @@ func IoctlSetPointerInt(fd int, req int, value int) error {
|
||||
return ioctlPtr(fd, req, unsafe.Pointer(&v))
|
||||
}
|
||||
|
||||
// IoctlSetString performs an ioctl operation which sets a string value
|
||||
// on fd, using the specified request number.
|
||||
func IoctlSetString(fd int, req int, value string) error {
|
||||
bs := append([]byte(value), 0)
|
||||
return ioctlPtr(fd, req, unsafe.Pointer(&bs[0]))
|
||||
}
|
||||
|
||||
// IoctlSetWinsize performs an ioctl on fd with a *Winsize argument.
|
||||
//
|
||||
// To change fd's window size, the req argument should be TIOCSWINSZ.
|
||||
|
||||
+8
-3
@@ -6,9 +6,7 @@
|
||||
|
||||
package unix
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
import "unsafe"
|
||||
|
||||
// ioctl itself should not be exposed directly, but additional get/set
|
||||
// functions for specific types are permissible.
|
||||
@@ -28,6 +26,13 @@ func IoctlSetPointerInt(fd int, req uint, value int) error {
|
||||
return ioctlPtr(fd, req, unsafe.Pointer(&v))
|
||||
}
|
||||
|
||||
// IoctlSetString performs an ioctl operation which sets a string value
|
||||
// on fd, using the specified request number.
|
||||
func IoctlSetString(fd int, req uint, value string) error {
|
||||
bs := append([]byte(value), 0)
|
||||
return ioctlPtr(fd, req, unsafe.Pointer(&bs[0]))
|
||||
}
|
||||
|
||||
// IoctlSetWinsize performs an ioctl on fd with a *Winsize argument.
|
||||
//
|
||||
// To change fd's window size, the req argument should be TIOCSWINSZ.
|
||||
|
||||
+4
-1
@@ -226,6 +226,7 @@ struct ltchars {
|
||||
#include <linux/cryptouser.h>
|
||||
#include <linux/devlink.h>
|
||||
#include <linux/dm-ioctl.h>
|
||||
#include <linux/elf.h>
|
||||
#include <linux/errqueue.h>
|
||||
#include <linux/ethtool_netlink.h>
|
||||
#include <linux/falloc.h>
|
||||
@@ -255,6 +256,7 @@ struct ltchars {
|
||||
#include <linux/loop.h>
|
||||
#include <linux/lwtunnel.h>
|
||||
#include <linux/magic.h>
|
||||
#include <linux/mei.h>
|
||||
#include <linux/memfd.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mount.h>
|
||||
@@ -529,6 +531,7 @@ ccflags="$@"
|
||||
$2 ~ /^O[CNPFPL][A-Z]+[^_][A-Z]+$/ ||
|
||||
$2 ~ /^(NL|CR|TAB|BS|VT|FF)DLY$/ ||
|
||||
$2 ~ /^(NL|CR|TAB|BS|VT|FF)[0-9]$/ ||
|
||||
$2 ~ /^(DT|EI|ELF|EV|NN|NT|PF|SHF|SHN|SHT|STB|STT|VER)_/ ||
|
||||
$2 ~ /^O?XTABS$/ ||
|
||||
$2 ~ /^TC[IO](ON|OFF)$/ ||
|
||||
$2 ~ /^IN_/ ||
|
||||
@@ -611,7 +614,7 @@ ccflags="$@"
|
||||
$2 !~ /IOC_MAGIC/ &&
|
||||
$2 ~ /^[A-Z][A-Z0-9_]+_MAGIC2?$/ ||
|
||||
$2 ~ /^(VM|VMADDR)_/ ||
|
||||
$2 ~ /^IOCTL_VM_SOCKETS_/ ||
|
||||
$2 ~ /^(IOCTL_VM_SOCKETS_|IOCTL_MEI_)/ ||
|
||||
$2 ~ /^(TASKSTATS|TS)_/ ||
|
||||
$2 ~ /^CGROUPSTATS_/ ||
|
||||
$2 ~ /^GENL_/ ||
|
||||
|
||||
+6
@@ -2643,3 +2643,9 @@ func SchedGetAttr(pid int, flags uint) (*SchedAttr, error) {
|
||||
|
||||
//sys Cachestat(fd uint, crange *CachestatRange, cstat *Cachestat_t, flags uint) (err error)
|
||||
//sys Mseal(b []byte, flags uint) (err error)
|
||||
|
||||
//sys setMemPolicy(mode int, mask *CPUSet, size int) (err error) = SYS_SET_MEMPOLICY
|
||||
|
||||
func SetMemPolicy(mode int, mask *CPUSet) error {
|
||||
return setMemPolicy(mode, mask, _CPU_SETSIZE)
|
||||
}
|
||||
|
||||
-8
@@ -1052,14 +1052,6 @@ func IoctlSetIntRetInt(fd int, req int, arg int) (int, error) {
|
||||
return ioctlRet(fd, req, uintptr(arg))
|
||||
}
|
||||
|
||||
func IoctlSetString(fd int, req int, val string) error {
|
||||
bs := make([]byte, len(val)+1)
|
||||
copy(bs[:len(bs)-1], val)
|
||||
err := ioctlPtr(fd, req, unsafe.Pointer(&bs[0]))
|
||||
runtime.KeepAlive(&bs[0])
|
||||
return err
|
||||
}
|
||||
|
||||
// Lifreq Helpers
|
||||
|
||||
func (l *Lifreq) SetName(name string) error {
|
||||
|
||||
+7
-3
@@ -367,7 +367,9 @@ func Recvmsg(fd int, p, oob []byte, flags int) (n, oobn int, recvflags int, from
|
||||
iov[0].SetLen(len(p))
|
||||
}
|
||||
var rsa RawSockaddrAny
|
||||
n, oobn, recvflags, err = recvmsgRaw(fd, iov[:], oob, flags, &rsa)
|
||||
if n, oobn, recvflags, err = recvmsgRaw(fd, iov[:], oob, flags, &rsa); err != nil {
|
||||
return
|
||||
}
|
||||
// source address is only specified if the socket is unconnected
|
||||
if rsa.Addr.Family != AF_UNSPEC {
|
||||
from, err = anyToSockaddr(fd, &rsa)
|
||||
@@ -389,8 +391,10 @@ func RecvmsgBuffers(fd int, buffers [][]byte, oob []byte, flags int) (n, oobn in
|
||||
}
|
||||
}
|
||||
var rsa RawSockaddrAny
|
||||
n, oobn, recvflags, err = recvmsgRaw(fd, iov, oob, flags, &rsa)
|
||||
if err == nil && rsa.Addr.Family != AF_UNSPEC {
|
||||
if n, oobn, recvflags, err = recvmsgRaw(fd, iov, oob, flags, &rsa); err != nil {
|
||||
return
|
||||
}
|
||||
if rsa.Addr.Family != AF_UNSPEC {
|
||||
from, err = anyToSockaddr(fd, &rsa)
|
||||
}
|
||||
return
|
||||
|
||||
+361
@@ -853,20 +853,86 @@ const (
|
||||
DM_VERSION_MAJOR = 0x4
|
||||
DM_VERSION_MINOR = 0x32
|
||||
DM_VERSION_PATCHLEVEL = 0x0
|
||||
DT_ADDRRNGHI = 0x6ffffeff
|
||||
DT_ADDRRNGLO = 0x6ffffe00
|
||||
DT_BLK = 0x6
|
||||
DT_CHR = 0x2
|
||||
DT_DEBUG = 0x15
|
||||
DT_DIR = 0x4
|
||||
DT_ENCODING = 0x20
|
||||
DT_FIFO = 0x1
|
||||
DT_FINI = 0xd
|
||||
DT_FLAGS_1 = 0x6ffffffb
|
||||
DT_GNU_HASH = 0x6ffffef5
|
||||
DT_HASH = 0x4
|
||||
DT_HIOS = 0x6ffff000
|
||||
DT_HIPROC = 0x7fffffff
|
||||
DT_INIT = 0xc
|
||||
DT_JMPREL = 0x17
|
||||
DT_LNK = 0xa
|
||||
DT_LOOS = 0x6000000d
|
||||
DT_LOPROC = 0x70000000
|
||||
DT_NEEDED = 0x1
|
||||
DT_NULL = 0x0
|
||||
DT_PLTGOT = 0x3
|
||||
DT_PLTREL = 0x14
|
||||
DT_PLTRELSZ = 0x2
|
||||
DT_REG = 0x8
|
||||
DT_REL = 0x11
|
||||
DT_RELA = 0x7
|
||||
DT_RELACOUNT = 0x6ffffff9
|
||||
DT_RELAENT = 0x9
|
||||
DT_RELASZ = 0x8
|
||||
DT_RELCOUNT = 0x6ffffffa
|
||||
DT_RELENT = 0x13
|
||||
DT_RELSZ = 0x12
|
||||
DT_RPATH = 0xf
|
||||
DT_SOCK = 0xc
|
||||
DT_SONAME = 0xe
|
||||
DT_STRSZ = 0xa
|
||||
DT_STRTAB = 0x5
|
||||
DT_SYMBOLIC = 0x10
|
||||
DT_SYMENT = 0xb
|
||||
DT_SYMTAB = 0x6
|
||||
DT_TEXTREL = 0x16
|
||||
DT_UNKNOWN = 0x0
|
||||
DT_VALRNGHI = 0x6ffffdff
|
||||
DT_VALRNGLO = 0x6ffffd00
|
||||
DT_VERDEF = 0x6ffffffc
|
||||
DT_VERDEFNUM = 0x6ffffffd
|
||||
DT_VERNEED = 0x6ffffffe
|
||||
DT_VERNEEDNUM = 0x6fffffff
|
||||
DT_VERSYM = 0x6ffffff0
|
||||
DT_WHT = 0xe
|
||||
ECHO = 0x8
|
||||
ECRYPTFS_SUPER_MAGIC = 0xf15f
|
||||
EFD_SEMAPHORE = 0x1
|
||||
EFIVARFS_MAGIC = 0xde5e81e4
|
||||
EFS_SUPER_MAGIC = 0x414a53
|
||||
EI_CLASS = 0x4
|
||||
EI_DATA = 0x5
|
||||
EI_MAG0 = 0x0
|
||||
EI_MAG1 = 0x1
|
||||
EI_MAG2 = 0x2
|
||||
EI_MAG3 = 0x3
|
||||
EI_NIDENT = 0x10
|
||||
EI_OSABI = 0x7
|
||||
EI_PAD = 0x8
|
||||
EI_VERSION = 0x6
|
||||
ELFCLASS32 = 0x1
|
||||
ELFCLASS64 = 0x2
|
||||
ELFCLASSNONE = 0x0
|
||||
ELFCLASSNUM = 0x3
|
||||
ELFDATA2LSB = 0x1
|
||||
ELFDATA2MSB = 0x2
|
||||
ELFDATANONE = 0x0
|
||||
ELFMAG = "\177ELF"
|
||||
ELFMAG0 = 0x7f
|
||||
ELFMAG1 = 'E'
|
||||
ELFMAG2 = 'L'
|
||||
ELFMAG3 = 'F'
|
||||
ELFOSABI_LINUX = 0x3
|
||||
ELFOSABI_NONE = 0x0
|
||||
EM_386 = 0x3
|
||||
EM_486 = 0x6
|
||||
EM_68K = 0x4
|
||||
@@ -1152,14 +1218,24 @@ const (
|
||||
ETH_P_WCCP = 0x883e
|
||||
ETH_P_X25 = 0x805
|
||||
ETH_P_XDSA = 0xf8
|
||||
ET_CORE = 0x4
|
||||
ET_DYN = 0x3
|
||||
ET_EXEC = 0x2
|
||||
ET_HIPROC = 0xffff
|
||||
ET_LOPROC = 0xff00
|
||||
ET_NONE = 0x0
|
||||
ET_REL = 0x1
|
||||
EV_ABS = 0x3
|
||||
EV_CNT = 0x20
|
||||
EV_CURRENT = 0x1
|
||||
EV_FF = 0x15
|
||||
EV_FF_STATUS = 0x17
|
||||
EV_KEY = 0x1
|
||||
EV_LED = 0x11
|
||||
EV_MAX = 0x1f
|
||||
EV_MSC = 0x4
|
||||
EV_NONE = 0x0
|
||||
EV_NUM = 0x2
|
||||
EV_PWR = 0x16
|
||||
EV_REL = 0x2
|
||||
EV_REP = 0x14
|
||||
@@ -1539,6 +1615,8 @@ const (
|
||||
IN_OPEN = 0x20
|
||||
IN_Q_OVERFLOW = 0x4000
|
||||
IN_UNMOUNT = 0x2000
|
||||
IOCTL_MEI_CONNECT_CLIENT = 0xc0104801
|
||||
IOCTL_MEI_CONNECT_CLIENT_VTAG = 0xc0144804
|
||||
IPPROTO_AH = 0x33
|
||||
IPPROTO_BEETPH = 0x5e
|
||||
IPPROTO_COMP = 0x6c
|
||||
@@ -2276,7 +2354,167 @@ const (
|
||||
NLM_F_REPLACE = 0x100
|
||||
NLM_F_REQUEST = 0x1
|
||||
NLM_F_ROOT = 0x100
|
||||
NN_386_IOPERM = "LINUX"
|
||||
NN_386_TLS = "LINUX"
|
||||
NN_ARC_V2 = "LINUX"
|
||||
NN_ARM_FPMR = "LINUX"
|
||||
NN_ARM_GCS = "LINUX"
|
||||
NN_ARM_HW_BREAK = "LINUX"
|
||||
NN_ARM_HW_WATCH = "LINUX"
|
||||
NN_ARM_PACA_KEYS = "LINUX"
|
||||
NN_ARM_PACG_KEYS = "LINUX"
|
||||
NN_ARM_PAC_ENABLED_KEYS = "LINUX"
|
||||
NN_ARM_PAC_MASK = "LINUX"
|
||||
NN_ARM_POE = "LINUX"
|
||||
NN_ARM_SSVE = "LINUX"
|
||||
NN_ARM_SVE = "LINUX"
|
||||
NN_ARM_SYSTEM_CALL = "LINUX"
|
||||
NN_ARM_TAGGED_ADDR_CTRL = "LINUX"
|
||||
NN_ARM_TLS = "LINUX"
|
||||
NN_ARM_VFP = "LINUX"
|
||||
NN_ARM_ZA = "LINUX"
|
||||
NN_ARM_ZT = "LINUX"
|
||||
NN_AUXV = "CORE"
|
||||
NN_FILE = "CORE"
|
||||
NN_GNU_PROPERTY_TYPE_0 = "GNU"
|
||||
NN_LOONGARCH_CPUCFG = "LINUX"
|
||||
NN_LOONGARCH_CSR = "LINUX"
|
||||
NN_LOONGARCH_HW_BREAK = "LINUX"
|
||||
NN_LOONGARCH_HW_WATCH = "LINUX"
|
||||
NN_LOONGARCH_LASX = "LINUX"
|
||||
NN_LOONGARCH_LBT = "LINUX"
|
||||
NN_LOONGARCH_LSX = "LINUX"
|
||||
NN_MIPS_DSP = "LINUX"
|
||||
NN_MIPS_FP_MODE = "LINUX"
|
||||
NN_MIPS_MSA = "LINUX"
|
||||
NN_PPC_DEXCR = "LINUX"
|
||||
NN_PPC_DSCR = "LINUX"
|
||||
NN_PPC_EBB = "LINUX"
|
||||
NN_PPC_HASHKEYR = "LINUX"
|
||||
NN_PPC_PKEY = "LINUX"
|
||||
NN_PPC_PMU = "LINUX"
|
||||
NN_PPC_PPR = "LINUX"
|
||||
NN_PPC_SPE = "LINUX"
|
||||
NN_PPC_TAR = "LINUX"
|
||||
NN_PPC_TM_CDSCR = "LINUX"
|
||||
NN_PPC_TM_CFPR = "LINUX"
|
||||
NN_PPC_TM_CGPR = "LINUX"
|
||||
NN_PPC_TM_CPPR = "LINUX"
|
||||
NN_PPC_TM_CTAR = "LINUX"
|
||||
NN_PPC_TM_CVMX = "LINUX"
|
||||
NN_PPC_TM_CVSX = "LINUX"
|
||||
NN_PPC_TM_SPR = "LINUX"
|
||||
NN_PPC_VMX = "LINUX"
|
||||
NN_PPC_VSX = "LINUX"
|
||||
NN_PRFPREG = "CORE"
|
||||
NN_PRPSINFO = "CORE"
|
||||
NN_PRSTATUS = "CORE"
|
||||
NN_PRXFPREG = "LINUX"
|
||||
NN_RISCV_CSR = "LINUX"
|
||||
NN_RISCV_TAGGED_ADDR_CTRL = "LINUX"
|
||||
NN_RISCV_VECTOR = "LINUX"
|
||||
NN_S390_CTRS = "LINUX"
|
||||
NN_S390_GS_BC = "LINUX"
|
||||
NN_S390_GS_CB = "LINUX"
|
||||
NN_S390_HIGH_GPRS = "LINUX"
|
||||
NN_S390_LAST_BREAK = "LINUX"
|
||||
NN_S390_PREFIX = "LINUX"
|
||||
NN_S390_PV_CPU_DATA = "LINUX"
|
||||
NN_S390_RI_CB = "LINUX"
|
||||
NN_S390_SYSTEM_CALL = "LINUX"
|
||||
NN_S390_TDB = "LINUX"
|
||||
NN_S390_TIMER = "LINUX"
|
||||
NN_S390_TODCMP = "LINUX"
|
||||
NN_S390_TODPREG = "LINUX"
|
||||
NN_S390_VXRS_HIGH = "LINUX"
|
||||
NN_S390_VXRS_LOW = "LINUX"
|
||||
NN_SIGINFO = "CORE"
|
||||
NN_TASKSTRUCT = "CORE"
|
||||
NN_VMCOREDD = "LINUX"
|
||||
NN_X86_SHSTK = "LINUX"
|
||||
NN_X86_XSAVE_LAYOUT = "LINUX"
|
||||
NN_X86_XSTATE = "LINUX"
|
||||
NSFS_MAGIC = 0x6e736673
|
||||
NT_386_IOPERM = 0x201
|
||||
NT_386_TLS = 0x200
|
||||
NT_ARC_V2 = 0x600
|
||||
NT_ARM_FPMR = 0x40e
|
||||
NT_ARM_GCS = 0x410
|
||||
NT_ARM_HW_BREAK = 0x402
|
||||
NT_ARM_HW_WATCH = 0x403
|
||||
NT_ARM_PACA_KEYS = 0x407
|
||||
NT_ARM_PACG_KEYS = 0x408
|
||||
NT_ARM_PAC_ENABLED_KEYS = 0x40a
|
||||
NT_ARM_PAC_MASK = 0x406
|
||||
NT_ARM_POE = 0x40f
|
||||
NT_ARM_SSVE = 0x40b
|
||||
NT_ARM_SVE = 0x405
|
||||
NT_ARM_SYSTEM_CALL = 0x404
|
||||
NT_ARM_TAGGED_ADDR_CTRL = 0x409
|
||||
NT_ARM_TLS = 0x401
|
||||
NT_ARM_VFP = 0x400
|
||||
NT_ARM_ZA = 0x40c
|
||||
NT_ARM_ZT = 0x40d
|
||||
NT_AUXV = 0x6
|
||||
NT_FILE = 0x46494c45
|
||||
NT_GNU_PROPERTY_TYPE_0 = 0x5
|
||||
NT_LOONGARCH_CPUCFG = 0xa00
|
||||
NT_LOONGARCH_CSR = 0xa01
|
||||
NT_LOONGARCH_HW_BREAK = 0xa05
|
||||
NT_LOONGARCH_HW_WATCH = 0xa06
|
||||
NT_LOONGARCH_LASX = 0xa03
|
||||
NT_LOONGARCH_LBT = 0xa04
|
||||
NT_LOONGARCH_LSX = 0xa02
|
||||
NT_MIPS_DSP = 0x800
|
||||
NT_MIPS_FP_MODE = 0x801
|
||||
NT_MIPS_MSA = 0x802
|
||||
NT_PPC_DEXCR = 0x111
|
||||
NT_PPC_DSCR = 0x105
|
||||
NT_PPC_EBB = 0x106
|
||||
NT_PPC_HASHKEYR = 0x112
|
||||
NT_PPC_PKEY = 0x110
|
||||
NT_PPC_PMU = 0x107
|
||||
NT_PPC_PPR = 0x104
|
||||
NT_PPC_SPE = 0x101
|
||||
NT_PPC_TAR = 0x103
|
||||
NT_PPC_TM_CDSCR = 0x10f
|
||||
NT_PPC_TM_CFPR = 0x109
|
||||
NT_PPC_TM_CGPR = 0x108
|
||||
NT_PPC_TM_CPPR = 0x10e
|
||||
NT_PPC_TM_CTAR = 0x10d
|
||||
NT_PPC_TM_CVMX = 0x10a
|
||||
NT_PPC_TM_CVSX = 0x10b
|
||||
NT_PPC_TM_SPR = 0x10c
|
||||
NT_PPC_VMX = 0x100
|
||||
NT_PPC_VSX = 0x102
|
||||
NT_PRFPREG = 0x2
|
||||
NT_PRPSINFO = 0x3
|
||||
NT_PRSTATUS = 0x1
|
||||
NT_PRXFPREG = 0x46e62b7f
|
||||
NT_RISCV_CSR = 0x900
|
||||
NT_RISCV_TAGGED_ADDR_CTRL = 0x902
|
||||
NT_RISCV_VECTOR = 0x901
|
||||
NT_S390_CTRS = 0x304
|
||||
NT_S390_GS_BC = 0x30c
|
||||
NT_S390_GS_CB = 0x30b
|
||||
NT_S390_HIGH_GPRS = 0x300
|
||||
NT_S390_LAST_BREAK = 0x306
|
||||
NT_S390_PREFIX = 0x305
|
||||
NT_S390_PV_CPU_DATA = 0x30e
|
||||
NT_S390_RI_CB = 0x30d
|
||||
NT_S390_SYSTEM_CALL = 0x307
|
||||
NT_S390_TDB = 0x308
|
||||
NT_S390_TIMER = 0x301
|
||||
NT_S390_TODCMP = 0x302
|
||||
NT_S390_TODPREG = 0x303
|
||||
NT_S390_VXRS_HIGH = 0x30a
|
||||
NT_S390_VXRS_LOW = 0x309
|
||||
NT_SIGINFO = 0x53494749
|
||||
NT_TASKSTRUCT = 0x4
|
||||
NT_VMCOREDD = 0x700
|
||||
NT_X86_SHSTK = 0x204
|
||||
NT_X86_XSAVE_LAYOUT = 0x205
|
||||
NT_X86_XSTATE = 0x202
|
||||
OCFS2_SUPER_MAGIC = 0x7461636f
|
||||
OCRNL = 0x8
|
||||
OFDEL = 0x80
|
||||
@@ -2463,6 +2701,59 @@ const (
|
||||
PERF_RECORD_MISC_USER = 0x2
|
||||
PERF_SAMPLE_BRANCH_PLM_ALL = 0x7
|
||||
PERF_SAMPLE_WEIGHT_TYPE = 0x1004000
|
||||
PF_ALG = 0x26
|
||||
PF_APPLETALK = 0x5
|
||||
PF_ASH = 0x12
|
||||
PF_ATMPVC = 0x8
|
||||
PF_ATMSVC = 0x14
|
||||
PF_AX25 = 0x3
|
||||
PF_BLUETOOTH = 0x1f
|
||||
PF_BRIDGE = 0x7
|
||||
PF_CAIF = 0x25
|
||||
PF_CAN = 0x1d
|
||||
PF_DECnet = 0xc
|
||||
PF_ECONET = 0x13
|
||||
PF_FILE = 0x1
|
||||
PF_IB = 0x1b
|
||||
PF_IEEE802154 = 0x24
|
||||
PF_INET = 0x2
|
||||
PF_INET6 = 0xa
|
||||
PF_IPX = 0x4
|
||||
PF_IRDA = 0x17
|
||||
PF_ISDN = 0x22
|
||||
PF_IUCV = 0x20
|
||||
PF_KCM = 0x29
|
||||
PF_KEY = 0xf
|
||||
PF_LLC = 0x1a
|
||||
PF_LOCAL = 0x1
|
||||
PF_MAX = 0x2e
|
||||
PF_MCTP = 0x2d
|
||||
PF_MPLS = 0x1c
|
||||
PF_NETBEUI = 0xd
|
||||
PF_NETLINK = 0x10
|
||||
PF_NETROM = 0x6
|
||||
PF_NFC = 0x27
|
||||
PF_PACKET = 0x11
|
||||
PF_PHONET = 0x23
|
||||
PF_PPPOX = 0x18
|
||||
PF_QIPCRTR = 0x2a
|
||||
PF_R = 0x4
|
||||
PF_RDS = 0x15
|
||||
PF_ROSE = 0xb
|
||||
PF_ROUTE = 0x10
|
||||
PF_RXRPC = 0x21
|
||||
PF_SECURITY = 0xe
|
||||
PF_SMC = 0x2b
|
||||
PF_SNA = 0x16
|
||||
PF_TIPC = 0x1e
|
||||
PF_UNIX = 0x1
|
||||
PF_UNSPEC = 0x0
|
||||
PF_VSOCK = 0x28
|
||||
PF_W = 0x2
|
||||
PF_WANPIPE = 0x19
|
||||
PF_X = 0x1
|
||||
PF_X25 = 0x9
|
||||
PF_XDP = 0x2c
|
||||
PID_FS_MAGIC = 0x50494446
|
||||
PIPEFS_MAGIC = 0x50495045
|
||||
PPPIOCGNPMODE = 0xc008744c
|
||||
@@ -2758,6 +3049,23 @@ const (
|
||||
PTRACE_SYSCALL_INFO_NONE = 0x0
|
||||
PTRACE_SYSCALL_INFO_SECCOMP = 0x3
|
||||
PTRACE_TRACEME = 0x0
|
||||
PT_AARCH64_MEMTAG_MTE = 0x70000002
|
||||
PT_DYNAMIC = 0x2
|
||||
PT_GNU_EH_FRAME = 0x6474e550
|
||||
PT_GNU_PROPERTY = 0x6474e553
|
||||
PT_GNU_RELRO = 0x6474e552
|
||||
PT_GNU_STACK = 0x6474e551
|
||||
PT_HIOS = 0x6fffffff
|
||||
PT_HIPROC = 0x7fffffff
|
||||
PT_INTERP = 0x3
|
||||
PT_LOAD = 0x1
|
||||
PT_LOOS = 0x60000000
|
||||
PT_LOPROC = 0x70000000
|
||||
PT_NOTE = 0x4
|
||||
PT_NULL = 0x0
|
||||
PT_PHDR = 0x6
|
||||
PT_SHLIB = 0x5
|
||||
PT_TLS = 0x7
|
||||
P_ALL = 0x0
|
||||
P_PGID = 0x2
|
||||
P_PID = 0x1
|
||||
@@ -3091,6 +3399,47 @@ const (
|
||||
SEEK_MAX = 0x4
|
||||
SEEK_SET = 0x0
|
||||
SELINUX_MAGIC = 0xf97cff8c
|
||||
SHF_ALLOC = 0x2
|
||||
SHF_EXCLUDE = 0x8000000
|
||||
SHF_EXECINSTR = 0x4
|
||||
SHF_GROUP = 0x200
|
||||
SHF_INFO_LINK = 0x40
|
||||
SHF_LINK_ORDER = 0x80
|
||||
SHF_MASKOS = 0xff00000
|
||||
SHF_MASKPROC = 0xf0000000
|
||||
SHF_MERGE = 0x10
|
||||
SHF_ORDERED = 0x4000000
|
||||
SHF_OS_NONCONFORMING = 0x100
|
||||
SHF_RELA_LIVEPATCH = 0x100000
|
||||
SHF_RO_AFTER_INIT = 0x200000
|
||||
SHF_STRINGS = 0x20
|
||||
SHF_TLS = 0x400
|
||||
SHF_WRITE = 0x1
|
||||
SHN_ABS = 0xfff1
|
||||
SHN_COMMON = 0xfff2
|
||||
SHN_HIPROC = 0xff1f
|
||||
SHN_HIRESERVE = 0xffff
|
||||
SHN_LIVEPATCH = 0xff20
|
||||
SHN_LOPROC = 0xff00
|
||||
SHN_LORESERVE = 0xff00
|
||||
SHN_UNDEF = 0x0
|
||||
SHT_DYNAMIC = 0x6
|
||||
SHT_DYNSYM = 0xb
|
||||
SHT_HASH = 0x5
|
||||
SHT_HIPROC = 0x7fffffff
|
||||
SHT_HIUSER = 0xffffffff
|
||||
SHT_LOPROC = 0x70000000
|
||||
SHT_LOUSER = 0x80000000
|
||||
SHT_NOBITS = 0x8
|
||||
SHT_NOTE = 0x7
|
||||
SHT_NULL = 0x0
|
||||
SHT_NUM = 0xc
|
||||
SHT_PROGBITS = 0x1
|
||||
SHT_REL = 0x9
|
||||
SHT_RELA = 0x4
|
||||
SHT_SHLIB = 0xa
|
||||
SHT_STRTAB = 0x3
|
||||
SHT_SYMTAB = 0x2
|
||||
SHUT_RD = 0x0
|
||||
SHUT_RDWR = 0x2
|
||||
SHUT_WR = 0x1
|
||||
@@ -3317,6 +3666,16 @@ const (
|
||||
STATX_UID = 0x8
|
||||
STATX_WRITE_ATOMIC = 0x10000
|
||||
STATX__RESERVED = 0x80000000
|
||||
STB_GLOBAL = 0x1
|
||||
STB_LOCAL = 0x0
|
||||
STB_WEAK = 0x2
|
||||
STT_COMMON = 0x5
|
||||
STT_FILE = 0x4
|
||||
STT_FUNC = 0x2
|
||||
STT_NOTYPE = 0x0
|
||||
STT_OBJECT = 0x1
|
||||
STT_SECTION = 0x3
|
||||
STT_TLS = 0x6
|
||||
SYNC_FILE_RANGE_WAIT_AFTER = 0x4
|
||||
SYNC_FILE_RANGE_WAIT_BEFORE = 0x1
|
||||
SYNC_FILE_RANGE_WRITE = 0x2
|
||||
@@ -3553,6 +3912,8 @@ const (
|
||||
UTIME_OMIT = 0x3ffffffe
|
||||
V9FS_MAGIC = 0x1021997
|
||||
VERASE = 0x2
|
||||
VER_FLG_BASE = 0x1
|
||||
VER_FLG_WEAK = 0x2
|
||||
VINTR = 0x0
|
||||
VKILL = 0x3
|
||||
VLNEXT = 0xf
|
||||
|
||||
+2
@@ -116,6 +116,8 @@ const (
|
||||
IEXTEN = 0x8000
|
||||
IN_CLOEXEC = 0x80000
|
||||
IN_NONBLOCK = 0x800
|
||||
IOCTL_MEI_NOTIFY_GET = 0x80044803
|
||||
IOCTL_MEI_NOTIFY_SET = 0x40044802
|
||||
IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9
|
||||
IPV6_FLOWINFO_MASK = 0xffffff0f
|
||||
IPV6_FLOWLABEL_MASK = 0xffff0f00
|
||||
|
||||
+2
@@ -116,6 +116,8 @@ const (
|
||||
IEXTEN = 0x8000
|
||||
IN_CLOEXEC = 0x80000
|
||||
IN_NONBLOCK = 0x800
|
||||
IOCTL_MEI_NOTIFY_GET = 0x80044803
|
||||
IOCTL_MEI_NOTIFY_SET = 0x40044802
|
||||
IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9
|
||||
IPV6_FLOWINFO_MASK = 0xffffff0f
|
||||
IPV6_FLOWLABEL_MASK = 0xffff0f00
|
||||
|
||||
+2
@@ -115,6 +115,8 @@ const (
|
||||
IEXTEN = 0x8000
|
||||
IN_CLOEXEC = 0x80000
|
||||
IN_NONBLOCK = 0x800
|
||||
IOCTL_MEI_NOTIFY_GET = 0x80044803
|
||||
IOCTL_MEI_NOTIFY_SET = 0x40044802
|
||||
IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9
|
||||
IPV6_FLOWINFO_MASK = 0xffffff0f
|
||||
IPV6_FLOWLABEL_MASK = 0xffff0f00
|
||||
|
||||
+2
@@ -120,6 +120,8 @@ const (
|
||||
IEXTEN = 0x8000
|
||||
IN_CLOEXEC = 0x80000
|
||||
IN_NONBLOCK = 0x800
|
||||
IOCTL_MEI_NOTIFY_GET = 0x80044803
|
||||
IOCTL_MEI_NOTIFY_SET = 0x40044802
|
||||
IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9
|
||||
IPV6_FLOWINFO_MASK = 0xffffff0f
|
||||
IPV6_FLOWLABEL_MASK = 0xffff0f00
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user